Property definitions

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