First-class modules and remove top-level object

Modules are now first-class objects in the VM. This allows us to remove
the top-level object, clean up core::bootstrap, and remove
core::globals (in favour of using core::bootstrap directly). As part of
this we also remove Trait.implement(), instead implementing traits using
compiler generated code. Trait.implement() could not be safely used
anyway, as it only copied over default methods; without verifying if all
required methods are implemented.

All of this makes it easier to implement the self-hosting compiler: the
top-level object no longer needs to be supported, and we can treat
core::bootstrap like any other module. This in turn allows us to use a
more efficient way of storing type information in various places.
parent a97f10be
Pipeline #111800657 passed with stages
in 15 minutes and 4 seconds
......@@ -78,7 +78,6 @@ require 'inkoc/pass/define_import_types'
require 'inkoc/pass/define_this_module_type'
require 'inkoc/pass/define_type'
require 'inkoc/pass/define_type_signatures'
require 'inkoc/pass/refine_module_type'
require 'inkoc/pass/validate_throw'
require 'inkoc/pass/implement_traits'
require 'inkoc/pass/optimize_keyword_arguments'
......
......@@ -65,7 +65,7 @@ module Inkoc
FileFlush
FileSize
FileSeek
LoadModule
ModuleLoad
SetAttribute
GetAttribute
SetPrototype
......@@ -78,7 +78,6 @@ module Inkoc
SetParentLocal
GetParentLocal
ObjectEquals
GetToplevel
GetNil
AttributeExists
RemoveAttribute
......@@ -95,7 +94,6 @@ module Inkoc
FloatGreaterOrEqual
FloatSmallerOrEqual
CopyBlocks
SetAttributeToObject
FloatIsNan
FloatIsInfinite
FloatFloor
......@@ -185,6 +183,10 @@ module Inkoc
RandomRange
RandomBytes
StringByte
ModuleList
ModuleGet
ModuleInfo
GetAttributeInSelf
]
.each_with_index
.each_with_object({}) { |(value, index), hash| hash[value] = index }
......
......@@ -4,7 +4,7 @@ module Inkoc
module Codegen
class Serializer
SIGNATURE = 'inko'.bytes
VERSION = 2
VERSION = 3
INTEGER_LITERAL = 0
FLOAT_LITERAL = 1
......
......@@ -14,7 +14,6 @@ module Inkoc
Pass::AddImplicitImportSymbols,
Pass::CompileImportedModules,
Pass::SetupSymbolTables,
Pass::RefineModuleType,
Pass::DefineThisModuleType,
Pass::DefineImportTypes,
Pass::DefineTypeSignatures,
......
......@@ -25,17 +25,8 @@ module Inkoc
# The path to the prelude module.
PRELUDE_MODULE = 'prelude'
# The path to the module that defines the default globals exposed to every
# module.
GLOBALS_MODULE = 'globals'
TRAIT_MODULE = 'trait'
INTERNAL_TRAIT_IMPORT = '_inkoc_std_trait'
MARKER_MODULE = 'std::marker'
INKO_CONST = 'Inko'
OBJECT_CONST = 'Object'
TRAIT_CONST = 'Trait'
ARRAY_CONST = 'Array'
......@@ -77,8 +68,8 @@ module Inkoc
TRY_BLOCK_NAME = '<try>'
ELSE_BLOCK_NAME = '<else>'
IMPL_NAME = '<impl>'
IMPLEMENT_TRAIT_MESSAGE = 'implement'
OBJECT_NAME_INSTANCE_ATTRIBUTE = '@_object_name'
IMPLEMENTED_TRAITS_INSTANCE_ATTRIBUTE = '@_implemented_traits'
INIT_MESSAGE = 'init'
RESERVED_CONSTANTS = Set.new(
......
......@@ -13,19 +13,11 @@ module Inkoc
end
def run(ast)
@module.type =
if @module.define_module?
define_module_type
else
typedb.top_level
end
@module.type = Inkoc::TypeSystem::Object
.new(name: @module.name.to_s, prototype: @state.typedb.module_type)
[ast]
end
def define_module_type
Inkoc::TypeSystem::Object.new(name: @module.name.to_s)
end
end
end
end
......@@ -886,10 +886,6 @@ module Inkoc
end
end
def on_raw_get_toplevel(*)
typedb.top_level.new_instance
end
def on_raw_set_prototype(node, _)
node.arguments.fetch(1).type
end
......@@ -898,10 +894,6 @@ module Inkoc
node.arguments.fetch(2).type
end
def on_raw_set_attribute_to_object(*)
typedb.new_empty_object.new_instance
end
def on_raw_get_attribute(node, *)
object = node.arguments.fetch(0).type
name = node.arguments.fetch(1)
......@@ -912,6 +904,7 @@ module Inkoc
TypeSystem::Dynamic.new
end
end
alias on_raw_get_attribute_in_self on_raw_get_attribute
def on_raw_set_object(node, *)
if (proto = node.arguments[1]&.type)
......@@ -1087,6 +1080,10 @@ module Inkoc
typedb.nil_type.new_instance
end
def on_raw_get_module_prototype(*)
typedb.module_type
end
def on_raw_run_block(*)
TypeSystem::Dynamic.new
end
......@@ -1591,6 +1588,22 @@ module Inkoc
node.arguments.fetch(1).type.new_instance
end
def on_raw_module_load(*)
typedb.module_type.new_instance
end
def on_raw_module_get(*)
TypeSystem::Optional.new(typedb.module_type.new_instance)
end
def on_raw_module_list(*)
typedb.new_array_of_type(typedb.module_type.new_instance)
end
def on_raw_module_info(*)
typedb.string_type.new_instance
end
def define_block_signature(node, scope, expected_block = nil)
define_type_parameters(node, scope)
define_argument_types(node, scope, expected_block)
......
......@@ -28,7 +28,14 @@ module Inkoc
.redefine_existing_constant_error(existing, node.location)
end
type = typedb.new_trait_type(node.name)
trait_proto = @module.globals[Config::TRAIT_CONST].type
unless trait_proto
raise "Trait's can't be defined until std::trait::Trait is defined." \
" This is likely a bootstrapping/compiler bug"
end
type = typedb.new_trait_type(node.name, trait_proto)
define_object_name_attribute(type)
define_required_traits(node, type, scope)
......
......@@ -38,10 +38,7 @@ module Inkoc
next if imported.include?(qname.to_s)
load_module(qname, body, import.location)
registers[qname.to_s] =
set_module_register(qname, body, import.location)
registers[qname.to_s] = load_module(qname, body, import.location)
imported << qname.to_s
end
......@@ -52,18 +49,12 @@ module Inkoc
def load_module(qname, body, location)
imported_mod = @state.module(qname)
import_path = imported_mod.bytecode_import_path
path_reg = set_string(import_path, body, location)
reg = body.register_dynamic
body.instruct(:Unary, :LoadModule, reg, path_reg, location)
end
def set_module_register(qname, body, location)
top_reg = get_toplevel(body, location)
mods_reg =
get_attribute(top_reg, Config::MODULES_ATTRIBUTE, body, location)
name_reg = set_string(qname.to_s, body, location)
path_reg = set_string(import_path, body, location)
reg = body.register(@module.type)
get_attribute(mods_reg, qname.to_s, body, location)
body.instruct(:Binary, :ModuleLoad, reg, name_reg, path_reg, location)
end
def on_module_body(node, body)
......@@ -79,51 +70,12 @@ module Inkoc
loc = @module.location
mod_reg = value_for_module_self(body)
mod_reg = set_global(Config::MODULE_GLOBAL, mod_reg, body, loc)
set_block_receiver(mod_reg, body, loc)
end
def value_for_module_self(body)
if @module.define_module?
define_module_object(body)
else
get_toplevel(body, @module.location)
end
end
def define_module_object(body)
loc = @module.location
top = get_toplevel(body, loc)
# Get the object containing all modules (Inko::modules).
modules = get_attribute(top, Config::MODULES_ATTRIBUTE, body, loc)
mod_reg = body.register(@module.type)
mod_name_reg = set_string(@module.name.to_s, body, loc)
# Get the prototype for the new module (Inko::Module)
proto = get_attribute(top, Config::MODULE_TYPE, body, loc)
body.instruct(:Unary, :ModuleGet, mod_reg, mod_name_reg, loc)
# Create the new module and store it in the modules list.
mod = initialize_module_type(body.type, proto, body, loc)
set_literal_attribute(modules, @module.name.to_s, mod, body, loc)
end
def initialize_module_type(type, receiver, body, location)
new_method = receiver.type.lookup_method(Config::NEW_MESSAGE)
module_name = set_string(@module.name.to_s, body, location)
file_path = set_current_file_path(body, location)
send_object_message(
receiver,
new_method.name,
[module_name, file_path],
[],
new_method,
type,
body,
location
)
set_global(Config::MODULE_GLOBAL, mod_reg, body, loc)
end
def set_current_file_path(body, location)
......@@ -407,8 +359,7 @@ module Inkoc
object =
if type.prototype
top = get_toplevel(body, loc)
proto = get_attribute(top, proto_name, body, loc)
proto = get_global(proto_name, body, loc)
set_object_with_prototype(type, true_reg, proto, body, loc)
else
......@@ -458,20 +409,30 @@ module Inkoc
trait
end
def implement_trait(object, trait, body, location)
message = Config::IMPLEMENT_TRAIT_MESSAGE
method = trait.type.lookup_method(message).type
def implement_trait(object, trait, body, loc)
name_reg =
set_string(Config::IMPLEMENTED_TRAITS_INSTANCE_ATTRIBUTE, body, loc)
send_object_message(
trait,
message,
[object],
[],
method,
method.return_type,
body,
location
)
traits_reg = body.register(typedb.new_empty_object.new_instance)
body.add_connected_basic_block
true_reg = get_true(body, loc)
body.instruct(:Binary, :GetAttributeInSelf, traits_reg, object, name_reg, loc)
body.instruct(:GotoNextBlockIfTrue, traits_reg, loc)
# traits object does not yet exist, create it.
proto_reg = get_global(Config::OBJECT_CONST, body, loc)
# create and store the "map" back in the object implementing the trait.
body.instruct(:SetObject, traits_reg, true_reg, proto_reg, loc)
body.instruct(:SetAttribute, traits_reg, object, name_reg, traits_reg, loc)
# register the trait and copy its blocks.
body.add_connected_basic_block
body.instruct(:CopyBlocks, object, trait, loc)
set_attribute(traits_reg, trait, true_reg, body, loc)
end
def on_reopen_object(node, body)
......@@ -753,10 +714,6 @@ module Inkoc
body.instruct(:Unary, :GetBuiltinPrototype, reg, id_reg, node.location)
end
def on_raw_get_toplevel(node, body)
get_toplevel(body, node.location)
end
def on_raw_set_prototype(node, body)
raw_binary_instruction(:SetPrototype, node, body)
end
......@@ -770,14 +727,14 @@ module Inkoc
set_attribute(receiver, name, value, body, node.location)
end
def on_raw_set_attribute_to_object(node, body)
raw_binary_instruction(:SetAttributeToObject, node, body)
end
def on_raw_get_attribute(node, body)
raw_binary_instruction(:GetAttribute, node, body)
end
def on_raw_get_attribute_in_self(node, body)
raw_binary_instruction(:GetAttributeInSelf, node, body)
end
def on_raw_set_object(node, body)
args = node.arguments
loc = node.location
......@@ -1292,6 +1249,10 @@ module Inkoc
builtin_prototype_instruction(PrototypeID::NIL, node, body)
end
def on_raw_get_module_prototype(node, body)
builtin_prototype_instruction(PrototypeID::MODULE, node, body)
end
def on_raw_get_byte_array_prototype(node, body)
builtin_prototype_instruction(PrototypeID::BYTE_ARRAY, node, body)
end
......@@ -1512,6 +1473,22 @@ module Inkoc
result
end
def on_raw_module_load(node, body)
raw_binary_instruction(:ModuleLoad, node, body)
end
def on_raw_module_list(node, body)
raw_nullary_instruction(:ModuleList, node, body)
end
def on_raw_module_get(node, body)
raw_unary_instruction(:ModuleGet, node, body)
end
def on_raw_module_info(node, body)
raw_binary_instruction(:ModuleInfo, node, body)
end
def on_return(node, body)
location = node.location
register =
......@@ -1729,22 +1706,10 @@ module Inkoc
)
end
def get_toplevel(body, location)
register = body.register(typedb.top_level)
body.instruct(:Nullary, :GetToplevel, register, location)
end
def get_self(body, location)
get_block_receiver(body, location)
end
def set_block_receiver(receiver, body, location)
register = body.register(receiver.type)
body.instruct(:Unary, :BlockSetReceiver, register, receiver, location)
end
def get_block_receiver(body, location)
register = body.register(body.self_type)
......
......@@ -18,22 +18,12 @@ module Inkoc
loc = ast.location
@module.imports << import_bootstrap(loc) if @module.import_bootstrap?
@module.imports << import_globals(loc) if @module.import_globals?
@module.imports << import_prelude(loc) if @module.import_prelude?
end
# Generates the import statement for importing the bootstrap module.
def import_bootstrap(location)
import_and_ignore(Config::BOOTSTRAP_MODULE, location)
end
# Generates the import statement for the globals module.
#
# Equivalent:
#
# import core::globals::*
def import_globals(location)
import_everything_from(Config::GLOBALS_MODULE, location)
import_everything_from(Config::BOOTSTRAP_MODULE, location)
end
# Generates the import statement for the prelude module.
......@@ -49,22 +39,6 @@ module Inkoc
AST::Identifier.new(name, location)
end
# Imports a module without exposing it as a global.
#
# Equivalent:
#
# import core::bootstrap::(self as _)
def import_and_ignore(name, location)
core = identifier_for(Config::CORE_MODULE, location)
bootstrap = identifier_for(name, location)
underscore = identifier_for('_', location)
symbol = AST::ImportSymbol
.new(AST::Self.new(location), underscore, location)
AST::Import.new([core, bootstrap], [symbol], location)
end
def import_everything_from(module_name, location)
core = identifier_for(Config::CORE_MODULE, location)
prelude = identifier_for(module_name, location)
......@@ -72,17 +46,6 @@ module Inkoc
AST::Import.new([core, prelude], [symbol], location)
end
def import_std_module_as(name, symbol_name, location)
std = identifier_for(Config::STD_MODULE, location)
name_ident = identifier_for(name, location)
alias_name = identifier_for(symbol_name, location)
symbol = AST::ImportSymbol
.new(AST::Self.new(location), alias_name, location)
AST::Import.new([std, name_ident], [symbol], location)
end
end
end
end
# frozen_string_literal: true
module Inkoc
module Pass
class RefineModuleType
def initialize(mod, state)
@module = mod
@state = state
end
def typedb
@state.typedb
end
def run(ast)
top = typedb.top_level
modules = top.lookup_attribute(Config::MODULES_ATTRIBUTE).type
proto = top.lookup_attribute(Config::MODULE_TYPE).type
@module.type.prototype = proto
modules.define_attribute(@module.type.name, @module.type)
[ast]
end
end
end
end
......@@ -11,5 +11,6 @@ module Inkoc
BOOLEAN = 6
BYTE_ARRAY = 7
NIL = 8
MODULE = 9
end
end
......@@ -66,21 +66,9 @@ module Inkoc
name = self.name.to_s
name != Config.core_module_name(Config::BOOTSTRAP_MODULE) &&
name != Config.core_module_name(Config::GLOBALS_MODULE) &&
name != Config.core_module_name(Config::PRELUDE_MODULE)
end
def import_globals?
name = self.name.to_s
name != Config.core_module_name(Config::BOOTSTRAP_MODULE) &&
name != Config.core_module_name(Config::GLOBALS_MODULE)
end
def define_module?
name.to_s != Config.core_module_name(Config::BOOTSTRAP_MODULE)
end
def source_code
location.file.path.read
end
......
......@@ -3,13 +3,13 @@
module Inkoc
module TypeSystem
class Database
attr_reader :top_level, :true_type, :false_type, :nil_type, :block_type,
attr_reader :true_type, :false_type, :nil_type, :block_type,
:integer_type, :float_type, :string_type, :array_type,
:object_type, :boolean_type, :file_type, :byte_array_type
:object_type, :boolean_type, :file_type, :byte_array_type,
:module_type
def initialize
@object_type = new_object_type(Config::OBJECT_CONST, nil)
@top_level = new_object_type(Config::INKO_CONST)
@boolean_type = new_object_type(Config::BOOLEAN_CONST)
@true_type = @boolean_type.new_instance
@false_type = @boolean_type.new_instance
......@@ -21,13 +21,10 @@ module Inkoc
@file_type = new_object_type(Config::FILE_CONST)
@byte_array_type = new_object_type(Config::BYTE_ARRAY_CONST)
@array_type = initialize_array_type
@module_type = new_object_type(Config::MODULE_TYPE)
@trait_id = -1
end
def trait_type
top_level.lookup_attribute(Config::TRAIT_CONST).type
end
def new_array_of_type(type)
array_type.new_instance([type])
end
......@@ -40,7 +37,7 @@ module Inkoc
Object.new(prototype: prototype)
end
def new_trait_type(name, proto = trait_type)
def new_trait_type(name, proto = nil)
Trait.new(name: name, prototype: proto, unique_id: @trait_id += 1)
end
......
......@@ -4,6 +4,19 @@ module Inkoc
module TypeSystem
class Error
include Type
include TypeWithAttributes