nitc :: SeparateCompiler :: defaultinit
# Singleton that store the knowledge about the separate compilation process
class SeparateCompiler
super AbstractCompiler
redef type VISITOR: SeparateCompilerVisitor
# The result of the RTA (used to know live types and methods)
var runtime_type_analysis: nullable RapidTypeAnalysis
private var undead_types: Set[MType] = new HashSet[MType]
private var live_unresolved_types: Map[MClassDef, Set[MType]] = new HashMap[MClassDef, HashSet[MType]]
private var type_ids: Map[MType, Int] is noinit
private var type_colors: Map[MType, Int] is noinit
private var opentype_colors: Map[MType, Int] is noinit
private var thunks_to_compile: Set[SeparateRuntimeFunction] = new HashSet[SeparateRuntimeFunction]
init do
var file = new_file("nit.common")
self.header = new CodeWriter(file)
self.compile_box_kinds
end
redef fun do_compilation
do
var compiler = self
compiler.compile_header
var c_name = mainmodule.c_name
# compile class structures
modelbuilder.toolcontext.info("Property coloring", 2)
compiler.new_file("{c_name}.classes")
compiler.do_property_coloring
compiler.compile_class_infos
for m in mainmodule.in_importation.greaters do
for mclass in m.intro_mclasses do
#if mclass.kind == abstract_kind or mclass.kind == interface_kind then continue
compiler.compile_class_to_c(mclass)
end
end
# The main function of the C
compiler.new_file("{c_name}.main")
compiler.compile_nitni_global_ref_functions
compiler.compile_main_function
compiler.compile_finalizer_function
compiler.link_mmethods
# compile methods
for m in mainmodule.in_importation.greaters do
modelbuilder.toolcontext.info("Generate C for module {m.full_name}", 2)
compiler.new_file("{m.c_name}.sep")
compiler.compile_module_to_c(m)
end
# compile live & cast type structures
modelbuilder.toolcontext.info("Type coloring", 2)
compiler.new_file("{c_name}.types")
compiler.compile_types
end
fun thunk_todo(thunk: SeparateRuntimeFunction)
do
# Concrete instance of `SeparateRuntimeFunction` are already
# handled by the compiler. Avoid duplicate compilation.
if thunk isa SeparateThunkFunction then
thunks_to_compile.add(thunk)
end
end
# Color and compile type structures and cast information
fun compile_types
do
var compiler = self
var mtypes = compiler.do_type_coloring
for t in mtypes do
compiler.compile_type_to_c(t)
end
# compile remaining types structures (useless but needed for the symbol resolution at link-time)
for t in compiler.undead_types do
if mtypes.has(t) then continue
compiler.compile_type_to_c(t)
end
end
redef fun compile_header_structs do
self.header.add_decl("typedef void(*nitmethod_t)(void); /* general C type representing a Nit method. */")
self.compile_header_attribute_structs
self.header.add_decl("struct class \{ int box_kind; nitmethod_t vft[]; \}; /* general C type representing a Nit class. */")
# With resolution_table_table, all live type resolution are stored in a big table: resolution_table
self.header.add_decl("struct type \{ int id; const char *name; int color; short int is_nullable; const struct types *resolution_table; int table_size; int type_table[]; \}; /* general C type representing a Nit type. */")
self.header.add_decl("struct instance \{ const struct type *type; const struct class *class; nitattribute_t attrs[]; \}; /* general C type representing a Nit instance. */")
self.header.add_decl("struct types \{ int dummy; const struct type *types[]; \}; /* a list types (used for vts, fts and unresolved lists). */")
self.header.add_decl("typedef struct instance val; /* general C type representing a Nit instance. */")
if not modelbuilder.toolcontext.opt_no_tag_primitives.value then
self.header.add_decl("extern const struct class *class_info[];")
self.header.add_decl("extern const struct type *type_info[];")
end
end
fun compile_header_attribute_structs
do
if modelbuilder.toolcontext.opt_no_union_attribute.value then
self.header.add_decl("typedef void* nitattribute_t; /* general C type representing a Nit attribute. */")
else
self.header.add_decl("typedef union \{")
self.header.add_decl("void* val;")
for c, v in self.box_kinds do
var t = c.mclass_type
# `Pointer` reuse the `val` field
if t.mclass.name == "Pointer" then continue
self.header.add_decl("{t.ctype_extern} {t.ctypename};")
end
self.header.add_decl("\} nitattribute_t; /* general C type representing a Nit attribute. */")
end
end
fun compile_box_kinds
do
# Collect all bas box class
# FIXME: this is not completely fine with a separate compilation scheme
for classname in ["Int", "Bool", "Byte", "Char", "Float", "CString",
"Pointer", "Int8", "Int16", "UInt16", "Int32", "UInt32"] do
var classes = self.mainmodule.model.get_mclasses_by_name(classname)
if classes == null then continue
assert classes.length == 1 else print_error classes.join(", ")
self.box_kinds[classes.first] = self.box_kinds.length + 1
end
end
var box_kinds = new HashMap[MClass, Int]
fun box_kind_of(mclass: MClass): Int
do
#var pointer_type = self.mainmodule.pointer_type
#if mclass.mclass_type.ctype == "val*" or mclass.mclass_type.is_subtype(self.mainmodule, mclass.mclass_type pointer_type) then
if mclass.mclass_type.ctype_extern == "val*" then
return 0
else if mclass.kind == extern_kind and mclass.name != "CString" then
return self.box_kinds[self.mainmodule.pointer_type.mclass]
else
return self.box_kinds[mclass]
end
end
fun compile_color_consts(colors: Map[Object, Int]) do
var v = new_visitor
for m, c in colors do
compile_color_const(v, m, c)
end
end
fun compile_color_const(v: SeparateCompilerVisitor, m: Object, color: Int) do
if color_consts_done.has(m) then return
if m isa MEntity then
if modelbuilder.toolcontext.opt_inline_coloring_numbers.value then
self.provide_declaration(m.const_color, "#define {m.const_color} {color}")
else if not modelbuilder.toolcontext.opt_colors_are_symbols.value or not v.compiler.target_platform.supports_linker_script then
self.provide_declaration(m.const_color, "extern const int {m.const_color};")
v.add("const int {m.const_color} = {color};")
else
# The color 'C' is the ``address'' of a false static variable 'XC'
self.provide_declaration(m.const_color, "#define {m.const_color} ((long)&X{m.const_color})\nextern const void X{m.const_color};")
if color == -1 then color = 0 # Symbols cannot be negative, so just use 0 for dead things
# Teach the linker that the address of 'XC' is `color`.
linker_script.add("X{m.const_color} = {color};")
end
else
abort
end
color_consts_done.add(m)
end
private var color_consts_done = new HashSet[Object]
# The conflict graph of classes used for coloration
var class_conflict_graph: POSetConflictGraph[MClass] is noinit
# colorize classe properties
fun do_property_coloring do
var rta = runtime_type_analysis
# Class graph
var mclasses = mainmodule.flatten_mclass_hierarchy
class_conflict_graph = mclasses.to_conflict_graph
# Prepare to collect elements to color and build layout with
var mmethods = new HashMap[MClass, Set[PropertyLayoutElement]]
var mattributes = new HashMap[MClass, Set[MAttribute]]
# The dead methods and super-call, still need to provide a dead color symbol
var dead_methods = new Array[PropertyLayoutElement]
for mclass in mclasses do
mmethods[mclass] = new HashSet[PropertyLayoutElement]
mattributes[mclass] = new HashSet[MAttribute]
end
# Pre-collect known live things
if rta != null then
for m in rta.live_methods do
mmethods[m.intro_mclassdef.mclass].add m
end
for m in rta.live_super_sends do
var mclass = m.mclassdef.mclass
mmethods[mclass].add m
end
end
for m in mainmodule.in_importation.greaters do for cd in m.mclassdefs do
var mclass = cd.mclass
# Collect methods and attributes
for p in cd.intro_mproperties do
if p isa MMethod then
if rta == null then
mmethods[mclass].add p
else if not rta.live_methods.has(p) then
dead_methods.add p
end
else if p isa MAttribute then
mattributes[mclass].add p
end
end
# Collect all super calls (dead or not)
for mpropdef in cd.mpropdefs do
if not mpropdef isa MMethodDef then continue
if mpropdef.has_supercall then
if rta == null then
mmethods[mclass].add mpropdef
else if not rta.live_super_sends.has(mpropdef) then
dead_methods.add mpropdef
end
end
end
end
# methods coloration
var meth_colorer = new POSetGroupColorer[MClass, PropertyLayoutElement](class_conflict_graph, mmethods)
var method_colors = meth_colorer.colors
compile_color_consts(method_colors)
# give null color to dead methods and supercalls
for mproperty in dead_methods do compile_color_const(new_visitor, mproperty, -1)
# attribute coloration
var attr_colorer = new POSetGroupColorer[MClass, MAttribute](class_conflict_graph, mattributes)
var attr_colors = attr_colorer.colors#ize(poset, mattributes)
compile_color_consts(attr_colors)
# Build method and attribute tables
method_tables = new HashMap[MClass, Array[nullable MPropDef]]
attr_tables = new HashMap[MClass, Array[nullable MProperty]]
for mclass in mclasses do
if not mclass.has_new_factory and (mclass.kind == abstract_kind or mclass.kind == interface_kind) then continue
if rta != null and not rta.live_classes.has(mclass) then continue
var mtype = mclass.intro.bound_mtype
# Resolve elements in the layout to get the final table
var meth_layout = meth_colorer.build_layout(mclass)
var meth_table = new Array[nullable MPropDef].with_capacity(meth_layout.length)
method_tables[mclass] = meth_table
for e in meth_layout do
if e == null then
meth_table.add null
else if e isa MMethod then
# Standard method call of `e`
meth_table.add e.lookup_first_definition(mainmodule, mtype)
else if e isa MMethodDef then
# Super-call in the methoddef `e`
meth_table.add e.lookup_next_definition(mainmodule, mtype)
else
abort
end
end
# Do not need to resolve attributes as only the position is used
attr_tables[mclass] = attr_colorer.build_layout(mclass)
end
end
# colorize live types of the program
private fun do_type_coloring: Collection[MType] do
# Collect types to colorize
var live_types = runtime_type_analysis.live_types
var live_cast_types = runtime_type_analysis.live_cast_types
var res = new HashSet[MType]
res.add_all live_types
res.add_all live_cast_types
if modelbuilder.toolcontext.opt_type_poset.value then
# Compute colors with a type poset
var poset = poset_from_mtypes(live_types, live_cast_types)
var colorer = new POSetColorer[MType]
colorer.colorize(poset)
type_ids = colorer.ids
type_colors = colorer.colors
type_tables = build_type_tables(poset)
else
# Compute colors using the class poset
# Faster to compute but the number of holes can degenerate
compute_type_test_layouts(live_types, live_cast_types)
type_ids = new HashMap[MType, Int]
for x in res do type_ids[x] = type_ids.length + 1
end
# VT and FT are stored with other unresolved types in the big resolution_tables
self.compute_resolution_tables(live_types)
return res
end
private fun poset_from_mtypes(mtypes, cast_types: Set[MType]): POSet[MType] do
var poset = new POSet[MType]
# Instead of doing the full matrix mtypes X cast_types,
# a grouping is done by the base classes of the type so
# that we compare only types whose base classes are in inheritance.
var mtypes_by_class = new MultiHashMap[MClass, MType]
for e in mtypes do
var c = e.undecorate.as(MClassType).mclass
mtypes_by_class[c].add(e)
poset.add_node(e)
end
var casttypes_by_class = new MultiHashMap[MClass, MType]
for e in cast_types do
var c = e.undecorate.as(MClassType).mclass
casttypes_by_class[c].add(e)
poset.add_node(e)
end
for c1, ts1 in mtypes_by_class do
for c2 in c1.in_hierarchy(mainmodule).greaters do
var ts2 = casttypes_by_class[c2]
for e in ts1 do
for o in ts2 do
if e == o then continue
if e.is_subtype(mainmodule, null, o) then
poset.add_edge(e, o)
end
end
end
end
end
return poset
end
# Build type tables
fun build_type_tables(mtypes: POSet[MType]): Map[MType, Array[nullable MType]] do
var tables = new HashMap[MType, Array[nullable MType]]
for mtype in mtypes do
var table = new Array[nullable MType]
for sup in mtypes[mtype].greaters do
var color = type_colors[sup]
if table.length <= color then
for i in [table.length .. color[ do
table[i] = null
end
end
table[color] = sup
end
tables[mtype] = table
end
return tables
end
private fun compute_type_test_layouts(mtypes: Set[MClassType], cast_types: Set[MType]) do
# Group cast_type by their classes
var bucklets = new HashMap[MClass, Set[MType]]
for e in cast_types do
var c = e.undecorate.as(MClassType).mclass
if not bucklets.has_key(c) then
bucklets[c] = new HashSet[MType]
end
bucklets[c].add(e)
end
# Colorize cast_types from the class hierarchy
var colorer = new POSetGroupColorer[MClass, MType](class_conflict_graph, bucklets)
type_colors = colorer.colors
var layouts = new HashMap[MClass, Array[nullable MType]]
for c in runtime_type_analysis.live_classes do
layouts[c] = colorer.build_layout(c)
end
# Build the table for each live type
for t in mtypes do
# A live type use the layout of its class
var c = t.mclass
var layout = layouts[c]
var table = new Array[nullable MType].with_capacity(layout.length)
type_tables[t] = table
# For each potential super-type in the layout
for sup in layout do
if sup == null then
table.add null
else if t.is_subtype(mainmodule, null, sup) then
table.add sup
else
table.add null
end
end
end
end
# resolution_tables is used to perform a type resolution at runtime in O(1)
private fun compute_resolution_tables(mtypes: Set[MType]) do
# During the visit of the body of classes, live_unresolved_types are collected
# and associated to
# Collect all live_unresolved_types (visited in the body of classes)
# Determinate fo each livetype what are its possible requested anchored types
var mtype2unresolved = new HashMap[MClass, Set[MType]]
for mtype in self.runtime_type_analysis.live_types do
var mclass = mtype.mclass
var set = mtype2unresolved.get_or_null(mclass)
if set == null then
set = new HashSet[MType]
mtype2unresolved[mclass] = set
end
for cd in mtype.collect_mclassdefs(self.mainmodule) do
if self.live_unresolved_types.has_key(cd) then
set.add_all(self.live_unresolved_types[cd])
end
end
end
# Compute the table layout with the prefered method
var colorer = new BucketsColorer[MClass, MType]
opentype_colors = colorer.colorize(mtype2unresolved)
resolution_tables = self.build_resolution_tables(self.runtime_type_analysis.live_types, mtype2unresolved)
# Compile a C constant for each collected unresolved type.
# Either to a color, or to -1 if the unresolved type is dead (no live receiver can require it)
var all_unresolved = new HashSet[MType]
for t in self.live_unresolved_types.values do
all_unresolved.add_all(t)
end
var all_unresolved_types_colors = new HashMap[MType, Int]
for t in all_unresolved do
if opentype_colors.has_key(t) then
all_unresolved_types_colors[t] = opentype_colors[t]
else
all_unresolved_types_colors[t] = -1
end
end
self.compile_color_consts(all_unresolved_types_colors)
#print "tables"
#for k, v in unresolved_types_tables.as(not null) do
# print "{k}: {v.join(", ")}"
#end
#print ""
end
fun build_resolution_tables(elements: Set[MClassType], map: Map[MClass, Set[MType]]): Map[MClassType, Array[nullable MType]] do
var tables = new HashMap[MClassType, Array[nullable MType]]
for mclasstype in elements do
var mtypes = map[mclasstype.mclass]
var table = new Array[nullable MType]
for mtype in mtypes do
var color = opentype_colors[mtype]
if table.length <= color then
for i in [table.length .. color[ do
table[i] = null
end
end
table[color] = mtype
end
tables[mclasstype] = table
end
return tables
end
# Separately compile all the method definitions of the module
fun compile_module_to_c(mmodule: MModule)
do
var old_module = self.mainmodule
self.mainmodule = mmodule
for cd in mmodule.mclassdefs do
for pd in cd.mpropdefs do
if not pd isa MMethodDef then continue
if pd.mproperty.is_broken or pd.is_broken or pd.msignature == null then continue # Skip broken method
var rta = runtime_type_analysis
if modelbuilder.toolcontext.opt_skip_dead_methods.value and rta != null and not rta.live_methoddefs.has(pd) then continue
#print "compile {pd} @ {cd} @ {mmodule}"
var r = pd.separate_runtime_function
r.compile_to_c(self)
var r2 = pd.virtual_runtime_function
if r2 != r then r2.compile_to_c(self)
# Generate trampolines
if modelbuilder.toolcontext.opt_trampoline_call.value then
r2.compile_trampolines(self)
end
end
end
var compiled_thunks = new Array[SeparateRuntimeFunction]
# Compile thunks here to write them in the same module they are declared.
for thunk in thunks_to_compile do
if thunk.mmethoddef.mclassdef.mmodule == mmodule then
thunk.compile_to_c(self)
compiled_thunks.add(thunk)
end
end
thunks_to_compile.remove_all(compiled_thunks)
self.mainmodule = old_module
end
# Process all introduced methods and compile some linking information (if needed)
fun link_mmethods
do
if not modelbuilder.toolcontext.opt_substitute_monomorph.value and not modelbuilder.toolcontext.opt_guard_call.value then return
for mmodule in mainmodule.in_importation.greaters do
for cd in mmodule.mclassdefs do
for m in cd.intro_mproperties do
if not m isa MMethod then continue
link_mmethod(m)
end
end
end
end
# Compile some linking information (if needed)
fun link_mmethod(m: MMethod)
do
var n2 = "CALL_" + m.const_color
# Replace monomorphic call by a direct call to the virtual implementation
var md = is_monomorphic(m)
if md != null then
linker_script.add("{n2} = {md.virtual_runtime_function.c_name};")
end
# If opt_substitute_monomorph then a trampoline is used, else a weak symbol is used
if modelbuilder.toolcontext.opt_guard_call.value then
var r = m.intro.virtual_runtime_function
provide_declaration(n2, "{r.c_ret} {n2}{r.c_sig} __attribute__((weak));")
end
end
# The single mmethodef called in case of monomorphism.
# Returns nul if dead or polymorphic.
fun is_monomorphic(m: MMethod): nullable MMethodDef
do
var rta = runtime_type_analysis
if rta == null then
# Without RTA, monomorphic means alone (uniq name)
if m.mpropdefs.length == 1 then
return m.mpropdefs.first
else
return null
end
else
# With RTA, monomorphic means only live methoddef
var res: nullable MMethodDef = null
for md in m.mpropdefs do
if rta.live_methoddefs.has(md) then
if res != null then return null
res = md
end
end
return res
end
end
# Globaly compile the type structure of a live type
fun compile_type_to_c(mtype: MType)
do
assert not mtype.need_anchor
var is_live = mtype isa MClassType and runtime_type_analysis.live_types.has(mtype)
var is_cast_live = runtime_type_analysis.live_cast_types.has(mtype)
var c_name = mtype.c_name
var v = new SeparateCompilerVisitor(self)
v.add_decl("/* runtime type {mtype} */")
# extern const struct type_X
self.provide_declaration("type_{c_name}", "extern const struct type type_{c_name};")
# const struct type_X
v.add_decl("const struct type type_{c_name} = \{")
# type id (for cast target)
if is_cast_live then
v.add_decl("{type_ids[mtype]},")
else
v.add_decl("-1, /*CAST DEAD*/")
end
# type name
v.add_decl("\"{mtype}\", /* class_name_string */")
# type color (for cast target)
if is_cast_live then
v.add_decl("{type_colors[mtype]},")
else
v.add_decl("-1, /*CAST DEAD*/")
end
# is_nullable bit
if mtype isa MNullableType then
v.add_decl("1,")
else
v.add_decl("0,")
end
# resolution table (for receiver)
if is_live then
var mclass_type = mtype.undecorate
assert mclass_type isa MClassType
if resolution_tables[mclass_type].is_empty then
v.add_decl("NULL, /*NO RESOLUTIONS*/")
else
compile_type_resolution_table(mtype)
v.require_declaration("resolution_table_{c_name}")
v.add_decl("&resolution_table_{c_name},")
end
else
v.add_decl("NULL, /*DEAD*/")
end
# cast table (for receiver)
if is_live then
v.add_decl("{self.type_tables[mtype].length},")
v.add_decl("\{")
for stype in self.type_tables[mtype] do
if stype == null then
v.add_decl("-1, /* empty */")
else
v.add_decl("{type_ids[stype]}, /* {stype} */")
end
end
v.add_decl("\},")
else
# Use -1 to indicate dead type, the info is used by --hardening
v.add_decl("-1, \{\}, /*DEAD TYPE*/")
end
v.add_decl("\};")
end
fun compile_type_resolution_table(mtype: MType) do
var mclass_type = mtype.undecorate.as(MClassType)
# extern const struct resolution_table_X resolution_table_X
self.provide_declaration("resolution_table_{mtype.c_name}", "extern const struct types resolution_table_{mtype.c_name};")
# const struct fts_table_X fts_table_X
var v = new_visitor
v.add_decl("const struct types resolution_table_{mtype.c_name} = \{")
v.add_decl("0, /* dummy */")
v.add_decl("\{")
for t in self.resolution_tables[mclass_type] do
if t == null then
v.add_decl("NULL, /* empty */")
else
# The table stores the result of the type resolution
# Therefore, for a receiver `mclass_type`, and a unresolved type `t`
# the value stored is tv.
var tv = t.resolve_for(mclass_type, mclass_type, self.mainmodule, true)
# FIXME: What typeids means here? How can a tv not be live?
if type_ids.has_key(tv) then
v.require_declaration("type_{tv.c_name}")
v.add_decl("&type_{tv.c_name}, /* {t}: {tv} */")
else
v.add_decl("NULL, /* empty ({t}: {tv} not a live type) */")
end
end
end
v.add_decl("\}")
v.add_decl("\};")
end
protected fun compile_class_vft(ccinfo: ClassCompilationInfo, v: SeparateCompilerVisitor)
do
var mclass = ccinfo.mclass
var mtype = ccinfo.mtype
var rta = runtime_type_analysis
var c_name = ccinfo.mclass.c_name
var is_dead = ccinfo.is_dead
var need_corpse = ccinfo.need_corpse
v.add_decl("/* runtime class {c_name}: {mclass.full_name} (dead={is_dead}; need_corpse={need_corpse})*/")
# Build class vft
if not is_dead or need_corpse then
self.provide_declaration("class_{c_name}", "extern const struct class class_{c_name};")
v.add_decl("const struct class class_{c_name} = \{")
v.add_decl("{self.box_kind_of(mclass)}, /* box_kind */")
v.add_decl("\{")
var vft = self.method_tables.get_or_null(mclass)
if vft != null then for i in [0 .. vft.length[ do
var mpropdef = vft[i]
if mpropdef == null then
v.add_decl("NULL, /* empty */")
else
assert mpropdef isa MMethodDef
if rta != null and not rta.live_methoddefs.has(mpropdef) then
v.add_decl("NULL, /* DEAD {mclass.intro_mmodule}:{mclass}:{mpropdef} */")
continue
else if mpropdef.is_broken or mpropdef.msignature == null or mpropdef.mproperty.is_broken then
v.add_decl("NULL, /* DEAD (BROKEN) {mclass.intro_mmodule}:{mclass}:{mpropdef} */")
continue
end
var rf = mpropdef.virtual_runtime_function
v.require_declaration(rf.c_name)
v.add_decl("(nitmethod_t){rf.c_name}, /* pointer to {mclass.intro_mmodule}:{mclass}:{mpropdef} */")
end
end
v.add_decl("\}")
v.add_decl("\};")
end
end
# Given a `MClass`, if it's a universal class and if it needs to be handle
# specifically by the compiler, this function will compile it and return
# true. Otherwise, no C code will be written in the visitor and the value
# false will be returned.
fun compile_class_if_universal(ccinfo: ClassCompilationInfo, v: SeparateCompilerVisitor): Bool
do
var mclass = ccinfo.mclass
var mtype = ccinfo.mtype
var c_name = ccinfo.mclass.c_name
var is_dead = ccinfo.is_dead
var need_corpse = ccinfo.need_corpse
if mtype.is_c_primitive or mtype.mclass.name == "Pointer" then
# Is a primitive type or the Pointer class, not any other extern class
if mtype.is_tagged then return true
#Build instance struct
self.header.add_decl("struct instance_{c_name} \{")
self.header.add_decl("const struct type *type;")
self.header.add_decl("const struct class *class;")
self.header.add_decl("{mtype.ctype_extern} value;")
self.header.add_decl("\};")
# Pointer is needed by extern types, live or not
if is_dead and mtype.mclass.name != "Pointer" then return true
#Build BOX
self.provide_declaration("BOX_{c_name}", "val* BOX_{c_name}({mtype.ctype_extern});")
v.add_decl("/* allocate {mtype} */")
v.add_decl("val* BOX_{mtype.c_name}({mtype.ctype_extern} value) \{")
var alloc = v.nit_alloc("sizeof(struct instance_{c_name})", mclass.full_name)
v.add("struct instance_{c_name}*res = {alloc};")
v.compiler.undead_types.add(mtype)
v.require_declaration("type_{c_name}")
v.add("res->type = &type_{c_name};")
v.require_declaration("class_{c_name}")
v.add("res->class = &class_{c_name};")
v.add("res->value = value;")
v.add("return (val*)res;")
v.add("\}")
# A Pointer class also need its constructor
if mtype.mclass.name != "Pointer" then return true
v = new_visitor
self.provide_declaration("NEW_{c_name}", "{mtype.ctype} NEW_{c_name}(const struct type* type);")
v.add_decl("/* allocate {mtype} */")
v.add_decl("{mtype.ctype} NEW_{c_name}(const struct type* type) \{")
if is_dead then
v.add_abort("{mclass} is DEAD")
else
var res = v.new_named_var(mtype, "self")
res.is_exact = true
alloc = v.nit_alloc("sizeof(struct instance_{mtype.c_name})", mclass.full_name)
v.add("{res} = {alloc};")
v.add("{res}->type = type;")
hardening_live_type(v, "type")
v.require_declaration("class_{c_name}")
v.add("{res}->class = &class_{c_name};")
v.add("((struct instance_{mtype.c_name}*){res})->value = NULL;")
v.add("return {res};")
end
v.add("\}")
return true
else if mclass.name == "NativeArray" then
#Build instance struct
self.header.add_decl("struct instance_{c_name} \{")
self.header.add_decl("const struct type *type;")
self.header.add_decl("const struct class *class;")
# NativeArrays are just a instance header followed by a length and an array of values
self.header.add_decl("int length;")
self.header.add_decl("val* values[0];")
self.header.add_decl("\};")
#Build NEW
self.provide_declaration("NEW_{c_name}", "{mtype.ctype} NEW_{c_name}(int length, const struct type* type);")
v.add_decl("/* allocate {mtype} */")
v.add_decl("{mtype.ctype} NEW_{c_name}(int length, const struct type* type) \{")
var res = v.get_name("self")
v.add_decl("struct instance_{c_name} *{res};")
var mtype_elt = mtype.arguments.first
var alloc = v.nit_alloc("sizeof(struct instance_{c_name}) + length*sizeof({mtype_elt.ctype})", mclass.full_name)
v.add("{res} = {alloc};")
v.add("{res}->type = type;")
hardening_live_type(v, "type")
v.require_declaration("class_{c_name}")
v.add("{res}->class = &class_{c_name};")
v.add("{res}->length = length;")
v.add("return (val*){res};")
v.add("\}")
return true
else if mclass.name == "RoutineRef" then
self.header.add_decl("struct instance_{c_name} \{")
self.header.add_decl("const struct type *type;")
self.header.add_decl("const struct class *class;")
self.header.add_decl("val* recv;")
self.header.add_decl("nitmethod_t method;")
self.header.add_decl("\};")
self.provide_declaration("NEW_{c_name}", "{mtype.ctype} NEW_{c_name}(val* recv, nitmethod_t method, const struct class* class, const struct type* type);")
v.add_decl("/* allocate {mtype} */")
v.add_decl("{mtype.ctype} NEW_{c_name}(val* recv, nitmethod_t method, const struct class* class, const struct type* type)\{")
var res = v.get_name("self")
v.add_decl("struct instance_{c_name} *{res};")
var alloc = v.nit_alloc("sizeof(struct instance_{c_name})", mclass.full_name)
v.add("{res} = {alloc};")
v.add("{res}->type = type;")
hardening_live_type(v, "type")
v.add("{res}->class = class;")
v.add("{res}->recv = recv;")
v.add("{res}->method = method;")
v.add("return (val*){res};")
v.add("\}")
return true
else if mtype.mclass.kind == extern_kind and mtype.mclass.name != "CString" then
# Is an extern class (other than Pointer and CString)
# Pointer is caught in a previous `if`, and CString is internal
var pointer_type = mainmodule.pointer_type
self.provide_declaration("NEW_{c_name}", "{mtype.ctype} NEW_{c_name}(const struct type* type);")
v.add_decl("/* allocate extern {mtype} */")
v.add_decl("{mtype.ctype} NEW_{c_name}(const struct type* type) \{")
if is_dead then
v.add_abort("{mclass} is DEAD")
else
var res = v.new_named_var(mtype, "self")
res.is_exact = true
var alloc = v.nit_alloc("sizeof(struct instance_{pointer_type.c_name})", mclass.full_name)
v.add("{res} = {alloc};")
v.add("{res}->type = type;")
hardening_live_type(v, "type")
v.require_declaration("class_{c_name}")
v.add("{res}->class = &class_{c_name};")
v.add("((struct instance_{pointer_type.c_name}*){res})->value = NULL;")
v.add("return {res};")
end
v.add("\}")
return true
end
return false
end
protected fun compile_default_new(ccinfo: ClassCompilationInfo, v: SeparateCompilerVisitor)
do
var mclass = ccinfo.mclass
var mtype = ccinfo.mtype
var c_name = ccinfo.mclass.c_name
var is_dead = ccinfo.is_dead
#Build NEW
self.provide_declaration("NEW_{c_name}", "{mtype.ctype} NEW_{c_name}(const struct type* type);")
v.add_decl("/* allocate {mtype} */")
v.add_decl("{mtype.ctype} NEW_{c_name}(const struct type* type) \{")
if is_dead then
v.add_abort("{mclass} is DEAD")
else
var res = v.new_named_var(mtype, "self")
res.is_exact = true
var attrs = self.attr_tables.get_or_null(mclass)
if attrs == null then
var alloc = v.nit_alloc("sizeof(struct instance)", mclass.full_name)
v.add("{res} = {alloc};")
else
var alloc = v.nit_alloc("sizeof(struct instance) + {attrs.length}*sizeof(nitattribute_t)", mclass.full_name)
v.add("{res} = {alloc};")
end
if modelbuilder.toolcontext.opt_trace.value then
v.add("tracepoint(Nit_Compiler, Object_Instance,\"{mtype}\", (uintptr_t)self);")
v.add("GC_register_finalizer(self, object_destroy_callback, NULL, NULL, NULL);")
end
v.add("{res}->type = type;")
hardening_live_type(v, "type")
v.require_declaration("class_{c_name}")
v.add("{res}->class = &class_{c_name};")
if attrs != null then
self.generate_init_attr(v, res, mtype)
v.set_finalizer res
end
v.add("return {res};")
end
v.add("\}")
end
protected fun build_class_compilation_info(mclass: MClass): ClassCompilationInfo
do
var mtype = mclass.intro.bound_mtype
var rta = runtime_type_analysis
var is_dead = rta != null and not rta.live_classes.has(mclass)
# While the class may be dead, some part of separately compiled code may use symbols associated to the class, so
# in order to compile and link correctly the C code, these symbols should be declared and defined.
var need_corpse = is_dead and mtype.is_c_primitive or mclass.kind == extern_kind or mclass.kind == enum_kind
var compilation_info = new ClassCompilationInfo(mclass, is_dead, need_corpse)
return compilation_info
end
# Globally compile the table of the class mclass
# In a link-time optimisation compiler, tables are globally computed
# In a true separate compiler (a with dynamic loading) you cannot do this unfortnally
fun compile_class_to_c(mclass: MClass)
do
var v = new_visitor
var class_info = build_class_compilation_info(mclass)
compile_class_vft(class_info, v)
var is_already_managed = compile_class_if_universal(class_info, v)
if not is_already_managed then
compile_default_new(class_info, v)
end
end
# Compile structures used to map tagged primitive values to their classes and types.
# This method also determines which class will be tagged.
fun compile_class_infos
do
if modelbuilder.toolcontext.opt_no_tag_primitives.value then return
# Note: if you change the tagging scheme, do not forget to update
# `autobox` and `extract_tag`
var class_info = new Array[nullable MClass].filled_with(null, 4)
for t in box_kinds.keys do
# Note: a same class can be associated to multiple slots if one want to
# use some Huffman coding.
if t.name == "Int" then
class_info[1] = t
t.mclass_type.tag_value = 1
else if t.name == "Char" then
class_info[2] = t
t.mclass_type.tag_value = 2
else if t.name == "Bool" then
class_info[3] = t
t.mclass_type.tag_value = 3
else
continue
end
t.mclass_type.is_tagged = true
end
# Compile the table for classes. The tag is used as an index
var v = self.new_visitor
v.add_decl "const struct class *class_info[4] = \{"
for t in class_info do
if t == null then
v.add_decl("NULL,")
else
var s = "class_{t.c_name}"
v.require_declaration(s)
v.add_decl("&{s},")
end
end
v.add_decl("\};")
# Compile the table for types. The tag is used as an index
v.add_decl "const struct type *type_info[4] = \{"
for t in class_info do
if t == null then
v.add_decl("NULL,")
else
var s = "type_{t.c_name}"
undead_types.add(t.mclass_type)
v.require_declaration(s)
v.add_decl("&{s},")
end
end
v.add_decl("\};")
end
# Add a dynamic test to ensure that the type referenced by `t` is a live type
fun hardening_live_type(v: VISITOR, t: String)
do
if not v.compiler.modelbuilder.toolcontext.opt_hardening.value then return
v.add("if({t} == NULL) \{")
v.add_abort("type null")
v.add("\}")
v.add("if({t}->table_size < 0) \{")
v.add("PRINT_ERROR(\"Instantiation of a dead type: %s\\n\", {t}->name);")
v.add_abort("type dead")
v.add("\}")
end
redef fun new_visitor do return new SeparateCompilerVisitor(self)
# Stats
private var type_tables: Map[MType, Array[nullable MType]] = new HashMap[MType, Array[nullable MType]]
private var resolution_tables: Map[MClassType, Array[nullable MType]] = new HashMap[MClassType, Array[nullable MType]]
protected var method_tables: Map[MClass, Array[nullable MPropDef]] = new HashMap[MClass, Array[nullable MPropDef]]
protected var attr_tables: Map[MClass, Array[nullable MProperty]] = new HashMap[MClass, Array[nullable MProperty]]
redef fun display_stats
do
super
if self.modelbuilder.toolcontext.opt_tables_metrics.value then
display_sizes
end
if self.modelbuilder.toolcontext.opt_isset_checks_metrics.value then
display_isset_checks
end
var tc = self.modelbuilder.toolcontext
tc.info("# implementation of method invocation",2)
var nb_invok_total = modelbuilder.nb_invok_by_tables + modelbuilder.nb_invok_by_direct + modelbuilder.nb_invok_by_inline
tc.info("total number of invocations: {nb_invok_total}",2)
tc.info("invocations by VFT send: {modelbuilder.nb_invok_by_tables} ({div(modelbuilder.nb_invok_by_tables,nb_invok_total)}%)",2)
tc.info("invocations by direct call: {modelbuilder.nb_invok_by_direct} ({div(modelbuilder.nb_invok_by_direct,nb_invok_total)}%)",2)
tc.info("invocations by inlining: {modelbuilder.nb_invok_by_inline} ({div(modelbuilder.nb_invok_by_inline,nb_invok_total)}%)",2)
end
fun display_sizes
do
print "# size of subtyping tables"
print "\ttotal \tholes"
var total = 0
var holes = 0
for t, table in type_tables do
total += table.length
for e in table do if e == null then holes += 1
end
print "\t{total}\t{holes}"
print "# size of resolution tables"
print "\ttotal \tholes"
total = 0
holes = 0
for t, table in resolution_tables do
total += table.length
for e in table do if e == null then holes += 1
end
print "\t{total}\t{holes}"
print "# size of methods tables"
print "\ttotal \tholes"
total = 0
holes = 0
for t, table in method_tables do
total += table.length
for e in table do if e == null then holes += 1
end
print "\t{total}\t{holes}"
print "# size of attributes tables"
print "\ttotal \tholes"
total = 0
holes = 0
for t, table in attr_tables do
total += table.length
for e in table do if e == null then holes += 1
end
print "\t{total}\t{holes}"
end
protected var isset_checks_count = 0
protected var attr_read_count = 0
fun display_isset_checks do
print "# total number of compiled attribute reads"
print "\t{attr_read_count}"
print "# total number of compiled isset-checks"
print "\t{isset_checks_count}"
end
redef fun compile_nitni_structs
do
self.header.add_decl """
struct nitni_instance \{
struct nitni_instance *next,
*prev; /* adjacent global references in global list */
int count; /* number of time this global reference has been marked */
struct instance *value;
\};
"""
super
end
redef fun finalize_ffi_for_module(mmodule)
do
var old_module = self.mainmodule
self.mainmodule = mmodule
super
self.mainmodule = old_module
end
end
src/compiler/separate_compiler.nit:137,1--1252,3