model: rename `as_notnullable` to `undecorate`
[nit.git] / src / compiler / separate_compiler.nit
index 9be2cd8..34e7be4 100644 (file)
@@ -29,8 +29,19 @@ redef class ToolContext
        var opt_no_union_attribute = new OptionBool("Put primitive attibutes in a box instead of an union", "--no-union-attribute")
        # --no-shortcut-equate
        var opt_no_shortcut_equate = new OptionBool("Always call == in a polymorphic way", "--no-shortcut-equal")
+       # --no-tag-primitives
+       var opt_no_tag_primitives = new OptionBool("Use only boxes for primitive types", "--no-tag-primitives")
+
        # --colors-are-symbols
-       var opt_colors_are_symbols = new OptionBool("Store colors as symbols (faster)", "--colors-are-symbols")
+       var opt_colors_are_symbols = new OptionBool("Store colors as symbols (link-boost)", "--colors-are-symbols")
+       # --trampoline-call
+       var opt_trampoline_call = new OptionBool("Use an indirection when calling", "--trampoline-call")
+       # --guard-call
+       var opt_guard_call = new OptionBool("Guard VFT calls with a direct call", "--guard-call")
+       # --substitute-monomorph
+       var opt_substitute_monomorph = new OptionBool("Replace monomorph trampoline with direct call (link-boost)", "--substitute-monomorph")
+       # --link-boost
+       var opt_link_boost = new OptionBool("Enable all link-boost optimizations", "--link-boost")
 
        # --inline-coloring-numbers
        var opt_inline_coloring_numbers = new OptionBool("Inline colors and ids (semi-global)", "--inline-coloring-numbers")
@@ -38,6 +49,8 @@ redef class ToolContext
        var opt_inline_some_methods = new OptionBool("Allow the separate compiler to inline some methods (semi-global)", "--inline-some-methods")
        # --direct-call-monomorph
        var opt_direct_call_monomorph = new OptionBool("Allow the separate compiler to direct call monomorph sites (semi-global)", "--direct-call-monomorph")
+       # --direct-call-monomorph0
+       var opt_direct_call_monomorph0 = new OptionBool("Allow the separate compiler to direct call monomorph sites (semi-global)", "--direct-call-monomorph0")
        # --skip-dead-methods
        var opt_skip_dead_methods = new OptionBool("Do not compile dead methods (semi-global)", "--skip-dead-methods")
        # --semi-global
@@ -46,6 +59,8 @@ redef class ToolContext
        var opt_colo_dead_methods = new OptionBool("Force colorization of dead methods", "--colo-dead-methods")
        # --tables-metrics
        var opt_tables_metrics = new OptionBool("Enable static size measuring of tables used for vft, typing and resolution", "--tables-metrics")
+       # --type-poset
+       var opt_type_poset = new OptionBool("Build a poset of types to create more condensed tables.", "--type-poset")
 
        redef init
        do
@@ -53,10 +68,13 @@ redef class ToolContext
                self.option_context.add_option(self.opt_separate)
                self.option_context.add_option(self.opt_no_inline_intern)
                self.option_context.add_option(self.opt_no_union_attribute)
-               self.option_context.add_option(self.opt_no_shortcut_equate, opt_colors_are_symbols)
+               self.option_context.add_option(self.opt_no_shortcut_equate)
+               self.option_context.add_option(self.opt_no_tag_primitives)
+               self.option_context.add_option(opt_colors_are_symbols, opt_trampoline_call, opt_guard_call, opt_direct_call_monomorph0, opt_substitute_monomorph, opt_link_boost)
                self.option_context.add_option(self.opt_inline_coloring_numbers, opt_inline_some_methods, opt_direct_call_monomorph, opt_skip_dead_methods, opt_semi_global)
                self.option_context.add_option(self.opt_colo_dead_methods)
                self.option_context.add_option(self.opt_tables_metrics)
+               self.option_context.add_option(self.opt_type_poset)
        end
 
        redef fun process_options(args)
@@ -70,6 +88,13 @@ redef class ToolContext
                        tc.opt_direct_call_monomorph.value = true
                        tc.opt_skip_dead_methods.value = true
                end
+               if tc.opt_link_boost.value then
+                       tc.opt_colors_are_symbols.value = true
+                       tc.opt_substitute_monomorph.value = true
+               end
+               if tc.opt_substitute_monomorph.value then
+                       tc.opt_trampoline_call.value = true
+               end
        end
 
        var separate_compiler_phase = new SeparateCompilerPhase(self, null)
@@ -124,8 +149,6 @@ class SeparateCompiler
        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
-       protected var method_colors: Map[PropertyLayoutElement, Int] is noinit
-       protected var attr_colors: Map[MAttribute, Int] is noinit
 
        init do
                var file = new_file("nit.common")
@@ -144,6 +167,7 @@ class SeparateCompiler
                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
@@ -156,6 +180,7 @@ class SeparateCompiler
                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
@@ -197,6 +222,11 @@ class SeparateCompiler
                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
@@ -239,7 +269,7 @@ class SeparateCompiler
                if mclass.mclass_type.ctype_extern == "val*" then
                        return 0
                else if mclass.kind == extern_kind and mclass.name != "NativeString" then
-                       return self.box_kinds[self.mainmodule.get_primitive_class("Pointer")]
+                       return self.box_kinds[self.mainmodule.pointer_type.mclass]
                else
                        return self.box_kinds[mclass]
                end
@@ -276,186 +306,180 @@ class SeparateCompiler
 
        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
 
-               # Layouts
-               var poset = mainmodule.flatten_mclass_hierarchy
-               var mclasses = new HashSet[MClass].from(poset)
-               var colorer = new POSetColorer[MClass]
-               colorer.colorize(poset)
-
-               # The dead methods, still need to provide a dead color symbol
-               var dead_methods = new Array[MMethod]
+               # Class graph
+               var mclasses = mainmodule.flatten_mclass_hierarchy
+               class_conflict_graph = mclasses.to_conflict_graph
 
-               # lookup properties to build layout with
+               # 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]
-                       for mprop in self.mainmodule.properties(mclass) do
-                               if mprop isa MMethod then
-                                       if not modelbuilder.toolcontext.opt_colo_dead_methods.value and rta != null and not rta.live_methods.has(mprop) then
-                                               dead_methods.add(mprop)
-                                               continue
-                                       end
-                                       mmethods[mclass].add(mprop)
-                               else if mprop isa MAttribute then
-                                       mattributes[mclass].add(mprop)
-                               end
+               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
 
-               # Collect all super calls (dead or not)
-               var all_super_calls = new HashSet[MMethodDef]
-               for mmodule in self.mainmodule.in_importation.greaters do
-                       for mclassdef in mmodule.mclassdefs do
-                               for mpropdef in mclassdef.mpropdefs do
-                                       if not mpropdef isa MMethodDef then continue
-                                       if mpropdef.has_supercall then
-                                               all_super_calls.add(mpropdef)
+               for m in mainmodule.in_importation.greaters do for cd in m.mclassdefs do
+                       var mclass = cd.mclass
+                       # Collect methods ad 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
-               end
-
-               # lookup super calls and add it to the list of mmethods to build layout with
-               var super_calls
-               if rta != null then
-                       super_calls = rta.live_super_sends
-               else
-                       super_calls = all_super_calls
-               end
 
-               for mmethoddef in super_calls do
-                       var mclass = mmethoddef.mclassdef.mclass
-                       mmethods[mclass].add(mmethoddef)
-                       for descendant in mclass.in_hierarchy(self.mainmodule).smallers do
-                               mmethods[descendant].add(mmethoddef)
+                       # 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 POSetBucketsColorer[MClass, PropertyLayoutElement](poset, colorer.conflicts)
-               method_colors = meth_colorer.colorize(mmethods)
-               method_tables = build_method_tables(mclasses, super_calls)
+               var meth_colorer = new POSetGroupColorer[MClass, PropertyLayoutElement](class_conflict_graph, mmethods)
+               var method_colors = meth_colorer.colors
                compile_color_consts(method_colors)
 
-               # attribute null color to dead methods and supercalls
-               for mproperty in dead_methods do
-                       compile_color_const(new_visitor, mproperty, -1)
-               end
-               for mpropdef in all_super_calls do
-                       if super_calls.has(mpropdef) then continue
-                       compile_color_const(new_visitor, mpropdef, -1)
-               end
+               # give null color to dead methods and supercalls
+               for mproperty in dead_methods do compile_color_const(new_visitor, mproperty, -1)
 
-               # attributes coloration
-               var attr_colorer = new POSetBucketsColorer[MClass, MAttribute](poset, colorer.conflicts)
-               attr_colors = attr_colorer.colorize(mattributes)
-               attr_tables = build_attr_tables(mclasses)
+               # 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)
-       end
 
-       fun build_method_tables(mclasses: Set[MClass], super_calls: Set[MMethodDef]): Map[MClass, Array[nullable MPropDef]] do
-               var tables = new HashMap[MClass, Array[nullable MPropDef]]
+               # 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
-                       var table = new Array[nullable MPropDef]
-                       tables[mclass] = table
+                       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 mproperties = self.mainmodule.properties(mclass)
                        var mtype = mclass.intro.bound_mtype
 
-                       for mproperty in mproperties do
-                               if not mproperty isa MMethod then continue
-                               if not method_colors.has_key(mproperty) then continue
-                               var color = method_colors[mproperty]
-                               if table.length <= color then
-                                       for i in [table.length .. color[ do
-                                               table[i] = null
-                                       end
-                               end
-                               table[color] = mproperty.lookup_first_definition(mainmodule, mtype)
-                       end
-
-                       for supercall in super_calls do
-                               if not mtype.collect_mclassdefs(mainmodule).has(supercall.mclassdef) then continue
-
-                               var color = method_colors[supercall]
-                               if table.length <= color then
-                                       for i in [table.length .. color[ do
-                                               table[i] = null
-                                       end
+                       # 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
-                               var mmethoddef = supercall.lookup_next_definition(mainmodule, mtype)
-                               table[color] = mmethoddef
                        end
 
+                       # Do not need to resolve attributes as only the position is used
+                       attr_tables[mclass] = attr_colorer.build_layout(mclass)
                end
-               return tables
-       end
-
-       fun build_attr_tables(mclasses: Set[MClass]): Map[MClass, Array[nullable MPropDef]] do
-               var tables = new HashMap[MClass, Array[nullable MPropDef]]
-               for mclass in mclasses do
-                       var table = new Array[nullable MPropDef]
-                       tables[mclass] = table
 
-                       var mproperties = self.mainmodule.properties(mclass)
-                       var mtype = mclass.intro.bound_mtype
 
-                       for mproperty in mproperties do
-                               if not mproperty isa MAttribute then continue
-                               if not attr_colors.has_key(mproperty) then continue
-                               var color = attr_colors[mproperty]
-                               if table.length <= color then
-                                       for i in [table.length .. color[ do
-                                               table[i] = null
-                                       end
-                               end
-                               table[color] = mproperty.lookup_first_definition(mainmodule, mtype)
-                       end
-               end
-               return tables
        end
 
        # colorize live types of the program
-       private fun do_type_coloring: POSet[MType] do
+       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 mtypes = new HashSet[MType]
-               mtypes.add_all(live_types)
-               for c in self.box_kinds.keys do
-                       mtypes.add(c.mclass_type)
-               end
 
-               # Compute colors
-               var poset = poset_from_mtypes(mtypes, 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)
+               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.compile_resolution_tables(mtypes)
+               self.compute_resolution_tables(live_types)
 
-               return poset
+               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)
-                       for o in cast_types do
-                               if e == o then continue
-                               poset.add_node(o)
-                               if e.is_subtype(mainmodule, null, o) then
-                                       poset.add_edge(e, o)
+               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
@@ -481,29 +505,75 @@ class SeparateCompiler
                return tables
        end
 
-       protected fun compile_resolution_tables(mtypes: Set[MType]) do
-               # resolution_tables is used to perform a type resolution at runtime in O(1)
 
+       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[MClassType, Set[MType]]
+               var mtype2unresolved = new HashMap[MClass, Set[MType]]
                for mtype in self.runtime_type_analysis.live_types do
-                       var set = new HashSet[MType]
+                       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
-                       mtype2unresolved[mtype] = set
                end
 
                # Compute the table layout with the prefered method
-               var colorer = new BucketsColorer[MType, MType]
+               var colorer = new BucketsColorer[MClass, MType]
+
                opentype_colors = colorer.colorize(mtype2unresolved)
-               resolution_tables = self.build_resolution_tables(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)
@@ -528,9 +598,10 @@ class SeparateCompiler
                #print ""
        end
 
-       fun build_resolution_tables(elements: Map[MClassType, Set[MType]]): Map[MClassType, Array[nullable MType]] do
+       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, mtypes in elements do
+               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]
@@ -561,11 +632,74 @@ class SeparateCompiler
                                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
                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
@@ -608,7 +742,7 @@ class SeparateCompiler
 
                # resolution table (for receiver)
                if is_live then
-                       var mclass_type = mtype.as_notnullable
+                       var mclass_type = mtype.undecorate
                        assert mclass_type isa MClassType
                        if resolution_tables[mclass_type].is_empty then
                                v.add_decl("NULL, /*NO RESOLUTIONS*/")
@@ -641,7 +775,7 @@ class SeparateCompiler
 
        fun compile_type_resolution_table(mtype: MType) do
 
-               var mclass_type = mtype.as_notnullable.as(MClassType)
+               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};")
@@ -680,12 +814,10 @@ class SeparateCompiler
                var mtype = mclass.intro.bound_mtype
                var c_name = mclass.c_name
 
-               var vft = self.method_tables[mclass]
-               var attrs = self.attr_tables[mclass]
                var v = new_visitor
 
                var rta = runtime_type_analysis
-               var is_dead = rta != null and not rta.live_classes.has(mclass) and mtype.ctype == "val*" and mclass.name != "NativeArray" and mclass.name != "Pointer"
+               var is_dead = rta != null and not rta.live_classes.has(mclass) and not mtype.is_c_primitive and mclass.name != "NativeArray" and mclass.name != "Pointer"
 
                v.add_decl("/* runtime class {c_name} */")
 
@@ -695,7 +827,8 @@ class SeparateCompiler
                        v.add_decl("const struct class class_{c_name} = \{")
                        v.add_decl("{self.box_kind_of(mclass)}, /* box_kind */")
                        v.add_decl("\{")
-                       for i in [0 .. vft.length[ do
+                       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 */")
@@ -714,9 +847,11 @@ class SeparateCompiler
                        v.add_decl("\};")
                end
 
-               if mtype.ctype != "val*" or mtype.mclass.name == "Pointer" then
+               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
+
                        #Build instance struct
                        self.header.add_decl("struct instance_{c_name} \{")
                        self.header.add_decl("const struct type *type;")
@@ -822,18 +957,78 @@ class SeparateCompiler
                else
                        var res = v.new_named_var(mtype, "self")
                        res.is_exact = true
-                       v.add("{res} = nit_alloc(sizeof(struct instance) + {attrs.length}*sizeof(nitattribute_t));")
+                       var attrs = self.attr_tables.get_or_null(mclass)
+                       if attrs == null then
+                               v.add("{res} = nit_alloc(sizeof(struct instance));")
+                       else
+                               v.add("{res} = nit_alloc(sizeof(struct instance) + {attrs.length}*sizeof(nitattribute_t));")
+                       end
                        v.add("{res}->type = type;")
                        hardening_live_type(v, "type")
                        v.require_declaration("class_{c_name}")
                        v.add("{res}->class = &class_{c_name};")
-                       self.generate_init_attr(v, res, mtype)
-                       v.set_finalizer res
+                       if attrs != null then
+                               self.generate_init_attr(v, res, mtype)
+                               v.set_finalizer res
+                       end
                        v.add("return {res};")
                end
                v.add("\}")
        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
+                       else if t.name == "Char" then
+                               class_info[2] = t
+                       else if t.name == "Bool" then
+                               class_info[3] = t
+                       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
@@ -854,7 +1049,7 @@ class SeparateCompiler
        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 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
@@ -990,11 +1185,33 @@ class SeparateCompilerVisitor
        do
                if value.mtype == mtype then
                        return value
-               else if value.mtype.ctype == "val*" and mtype.ctype == "val*" then
+               else if not value.mtype.is_c_primitive and not mtype.is_c_primitive then
                        return value
-               else if value.mtype.ctype == "val*" then
+               else if not value.mtype.is_c_primitive then
+                       if mtype.is_tagged then
+                               if mtype.name == "Int" then
+                                       return self.new_expr("(long)({value})>>2", mtype)
+                               else if mtype.name == "Char" then
+                                       return self.new_expr("(char)((long)({value})>>2)", mtype)
+                               else if mtype.name == "Bool" then
+                                       return self.new_expr("(short int)((long)({value})>>2)", mtype)
+                               else
+                                       abort
+                               end
+                       end
                        return self.new_expr("((struct instance_{mtype.c_name}*){value})->value; /* autounbox from {value.mtype} to {mtype} */", mtype)
-               else if mtype.ctype == "val*" then
+               else if not mtype.is_c_primitive then
+                       if value.mtype.is_tagged then
+                               if value.mtype.name == "Int" then
+                                       return self.new_expr("(val*)({value}<<2|1)", mtype)
+                               else if value.mtype.name == "Char" then
+                                       return self.new_expr("(val*)((long)({value})<<2|2)", mtype)
+                               else if value.mtype.name == "Bool" then
+                                       return self.new_expr("(val*)((long)({value})<<2|3)", mtype)
+                               else
+                                       abort
+                               end
+                       end
                        var valtype = value.mtype.as(MClassType)
                        if mtype isa MClassType and mtype.mclass.kind == extern_kind and mtype.mclass.name != "NativeString" then
                                valtype = compiler.mainmodule.pointer_type
@@ -1002,7 +1219,7 @@ class SeparateCompilerVisitor
                        var res = self.new_var(mtype)
                        if compiler.runtime_type_analysis != null and not compiler.runtime_type_analysis.live_types.has(valtype) then
                                self.add("/*no autobox from {value.mtype} to {mtype}: {value.mtype} is not live! */")
-                               self.add("PRINT_ERROR(\"Dead code executed!\\n\"); show_backtrace(1);")
+                               self.add("PRINT_ERROR(\"Dead code executed!\\n\"); fatal_exit(1);")
                                return res
                        end
                        self.require_declaration("BOX_{valtype.c_name}")
@@ -1016,7 +1233,7 @@ class SeparateCompilerVisitor
                        # Bad things will appen!
                        var res = self.new_var(mtype)
                        self.add("/* {res} left unintialized (cannot convert {value.mtype} to {mtype}) */")
-                       self.add("PRINT_ERROR(\"Cast error: Cannot cast %s to %s.\\n\", \"{value.mtype}\", \"{mtype}\"); show_backtrace(1);")
+                       self.add("PRINT_ERROR(\"Cast error: Cannot cast %s to %s.\\n\", \"{value.mtype}\", \"{mtype}\"); fatal_exit(1);")
                        return res
                end
        end
@@ -1042,7 +1259,7 @@ class SeparateCompilerVisitor
                        var res = self.new_var(mtype)
                        if compiler.runtime_type_analysis != null and not compiler.runtime_type_analysis.live_types.has(value.mtype.as(MClassType)) then
                                self.add("/*no boxing of {value.mtype}: {value.mtype} is not live! */")
-                               self.add("PRINT_ERROR(\"Dead code executed!\\n\"); show_backtrace(1);")
+                               self.add("PRINT_ERROR(\"Dead code executed!\\n\"); fatal_exit(1);")
                                return res
                        end
                        self.require_declaration("BOX_{valtype.c_name}")
@@ -1057,11 +1274,42 @@ class SeparateCompilerVisitor
                end
        end
 
-       # Return a C expression returning the runtime type structure of the value
-       # The point of the method is to works also with primitives types.
+       # Returns a C expression containing the tag of the value as a long.
+       #
+       # If the C expression is evaluated to 0, it means there is no tag.
+       # Thus the expression can be used as a condition.
+       fun extract_tag(value: RuntimeVariable): String
+       do
+               assert not value.mtype.is_c_primitive
+               return "((long){value}&3)" # Get the two low bits
+       end
+
+       # Returns a C expression of the runtime class structure of the value.
+       # The point of the method is to work also with primitive types.
+       fun class_info(value: RuntimeVariable): String
+       do
+               if not value.mtype.is_c_primitive then
+                       if can_be_primitive(value) and not compiler.modelbuilder.toolcontext.opt_no_tag_primitives.value then
+                               var tag = extract_tag(value)
+                               return "({tag}?class_info[{tag}]:{value}->class)"
+                       end
+                       return "{value}->class"
+               else
+                       compiler.undead_types.add(value.mtype)
+                       self.require_declaration("class_{value.mtype.c_name}")
+                       return "(&class_{value.mtype.c_name})"
+               end
+       end
+
+       # Returns a C expression of the runtime type structure of the value.
+       # The point of the method is to work also with primitive types.
        fun type_info(value: RuntimeVariable): String
        do
-               if value.mtype.ctype == "val*" then
+               if not value.mtype.is_c_primitive then
+                       if can_be_primitive(value) and not compiler.modelbuilder.toolcontext.opt_no_tag_primitives.value then
+                               var tag = extract_tag(value)
+                               return "({tag}?type_info[{tag}]:{value}->type)"
+                       end
                        return "{value}->type"
                else
                        compiler.undead_types.add(value.mtype)
@@ -1073,28 +1321,40 @@ class SeparateCompilerVisitor
        redef fun compile_callsite(callsite, args)
        do
                var rta = compiler.runtime_type_analysis
-               var mmethod = callsite.mproperty
                # TODO: Inlining of new-style constructors with initializers
                if compiler.modelbuilder.toolcontext.opt_direct_call_monomorph.value and rta != null and callsite.mpropdef.initializers.is_empty then
                        var tgs = rta.live_targets(callsite)
                        if tgs.length == 1 then
-                               # DIRECT CALL
-                               var res0 = before_send(mmethod, args)
-                               var res = call(tgs.first, tgs.first.mclassdef.bound_mtype, args)
-                               if res0 != null then
-                                       assert res != null
-                                       self.assign(res0, res)
-                                       res = res0
-                               end
-                               add("\}") # close the before_send
-                               return res
+                               return direct_call(tgs.first, args)
                        end
                end
+               # Shortcut intern methods as they are not usually redefinable
+               if callsite.mpropdef.is_intern and callsite.mproperty.name != "object_id" then
+                       # `object_id` is the only redefined intern method, so it can not be directly called.
+                       # TODO find a less ugly approach?
+                       return direct_call(callsite.mpropdef, args)
+               end
                return super
        end
+
+       # Fully and directly call a mpropdef
+       #
+       # This method is used by `compile_callsite`
+       private fun direct_call(mpropdef: MMethodDef, args: Array[RuntimeVariable]): nullable RuntimeVariable
+       do
+               var res0 = before_send(mpropdef.mproperty, args)
+               var res = call(mpropdef, mpropdef.mclassdef.bound_mtype, args)
+               if res0 != null then
+                       assert res != null
+                       self.assign(res0, res)
+                       res = res0
+               end
+               add("\}") # close the before_send
+               return res
+       end
        redef fun send(mmethod, arguments)
        do
-               if arguments.first.mcasttype.ctype != "val*" then
+               if arguments.first.mcasttype.is_c_primitive then
                        # In order to shortcut the primitive, we need to find the most specific method
                        # Howverr, because of performance (no flattening), we always work on the realmainmodule
                        var m = self.compiler.mainmodule
@@ -1104,7 +1364,7 @@ class SeparateCompilerVisitor
                        return res
                end
 
-               return table_send(mmethod, arguments, mmethod.const_color)
+               return table_send(mmethod, arguments, mmethod)
        end
 
        # Handle common special cases before doing the effective method invocation
@@ -1170,7 +1430,7 @@ class SeparateCompilerVisitor
                return res
        end
 
-       private fun table_send(mmethod: MMethod, arguments: Array[RuntimeVariable], const_color: String): nullable RuntimeVariable
+       private fun table_send(mmethod: MMethod, arguments: Array[RuntimeVariable], mentity: MEntity): nullable RuntimeVariable
        do
                compiler.modelbuilder.nb_invok_by_tables += 1
                if compiler.modelbuilder.toolcontext.opt_invocation_metrics.value then add("count_invoke_by_tables++;")
@@ -1180,8 +1440,10 @@ class SeparateCompilerVisitor
 
                var res0 = before_send(mmethod, arguments)
 
+               var runtime_function = mmethod.intro.virtual_runtime_function
+               var msignature = runtime_function.called_signature
+
                var res: nullable RuntimeVariable
-               var msignature = mmethod.intro.msignature.resolve_for(mmethod.intro.mclassdef.bound_mtype, mmethod.intro.mclassdef.bound_mtype, mmethod.intro.mclassdef.mmodule, true)
                var ret = msignature.return_mtype
                if ret == null then
                        res = null
@@ -1189,10 +1451,8 @@ class SeparateCompilerVisitor
                        res = self.new_var(ret)
                end
 
-               var s = new FlatBuffer
                var ss = new FlatBuffer
 
-               s.append("val*")
                ss.append("{recv}")
                for i in [0..msignature.arity[ do
                        var a = arguments[i+1]
@@ -1200,21 +1460,46 @@ class SeparateCompilerVisitor
                        if i == msignature.vararg_rank then
                                t = arguments[i+1].mcasttype
                        end
-                       s.append(", {t.ctype}")
                        a = self.autobox(a, t)
                        ss.append(", {a}")
                end
 
-
-               var r
-               if ret == null then r = "void" else r = ret.ctype
-               self.require_declaration(const_color)
-               var call = "(({r} (*)({s}))({arguments.first}->class->vft[{const_color}]))({ss}) /* {mmethod} on {arguments.first.inspect}*/"
-
+               var const_color = mentity.const_color
+               var ress
                if res != null then
-                       self.add("{res} = {call};")
+                       ress = "{res} = "
                else
-                       self.add("{call};")
+                       ress = ""
+               end
+               if mentity isa MMethod and compiler.modelbuilder.toolcontext.opt_direct_call_monomorph0.value then
+                       # opt_direct_call_monomorph0 is used to compare the efficiency of the alternative lookup implementation, ceteris paribus.
+                       # The difference with the non-zero option is that the monomorphism is looked-at on the mmethod level and not at the callsite level.
+                       # TODO: remove this mess and use per callsite service to detect monomorphism in a single place.
+                       var md = compiler.is_monomorphic(mentity)
+                       if md != null then
+                               var callsym = md.virtual_runtime_function.c_name
+                               self.require_declaration(callsym)
+                               self.add "{ress}{callsym}({ss}); /* {mmethod} on {arguments.first.inspect}*/"
+                       else
+                               self.require_declaration(const_color)
+                               self.add "{ress}(({runtime_function.c_funptrtype})({class_info(arguments.first)}->vft[{const_color}]))({ss}); /* {mmethod} on {arguments.first.inspect}*/"
+                       end
+               else if mentity isa MMethod and compiler.modelbuilder.toolcontext.opt_guard_call.value then
+                       var callsym = "CALL_" + const_color
+                       self.require_declaration(callsym)
+                       self.add "if (!{callsym}) \{"
+                       self.require_declaration(const_color)
+                       self.add "{ress}(({runtime_function.c_funptrtype})({class_info(arguments.first)}->vft[{const_color}]))({ss}); /* {mmethod} on {arguments.first.inspect}*/"
+                       self.add "\} else \{"
+                       self.add "{ress}{callsym}({ss}); /* {mmethod} on {arguments.first.inspect}*/"
+                       self.add "\}"
+               else if mentity isa MMethod and compiler.modelbuilder.toolcontext.opt_trampoline_call.value then
+                       var callsym = "CALL_" + const_color
+                       self.require_declaration(callsym)
+                       self.add "{ress}{callsym}({ss}); /* {mmethod} on {arguments.first.inspect}*/"
+               else
+                       self.require_declaration(const_color)
+                       self.add "{ress}(({runtime_function.c_funptrtype})({class_info(arguments.first)}->vft[{const_color}]))({ss}); /* {mmethod} on {arguments.first.inspect}*/"
                end
 
                if res0 != null then
@@ -1276,7 +1561,7 @@ class SeparateCompilerVisitor
 
        redef fun supercall(m: MMethodDef, recvtype: MClassType, arguments: Array[RuntimeVariable]): nullable RuntimeVariable
        do
-               if arguments.first.mcasttype.ctype != "val*" then
+               if arguments.first.mcasttype.is_c_primitive then
                        # In order to shortcut the primitive, we need to find the most specific method
                        # However, because of performance (no flattening), we always work on the realmainmodule
                        var main = self.compiler.mainmodule
@@ -1285,7 +1570,7 @@ class SeparateCompilerVisitor
                        self.compiler.mainmodule = main
                        return res
                end
-               return table_send(m.mproperty, arguments, m.const_color)
+               return table_send(m.mproperty, arguments, m)
        end
 
        redef fun vararg_instance(mpropdef, recv, varargs, elttype)
@@ -1327,7 +1612,7 @@ class SeparateCompilerVisitor
                        self.add("{res} = {recv}->attrs[{a.const_color}] != NULL; /* {a} on {recv.inspect}*/")
                else
 
-                       if mtype.ctype == "val*" then
+                       if not mtype.is_c_primitive and not mtype.is_tagged then
                                self.add("{res} = {recv}->attrs[{a.const_color}].val != NULL; /* {a} on {recv.inspect} */")
                        else
                                self.add("{res} = 1; /* NOT YET IMPLEMENTED: isset of primitives: {a} on {recv.inspect} */")
@@ -1379,7 +1664,7 @@ class SeparateCompilerVisitor
                        self.add("{res} = {recv}->attrs[{a.const_color}].{ret.ctypename}; /* {a} on {recv.inspect} */")
 
                        # Check for Uninitialized attribute
-                       if ret.ctype == "val*" and not ret isa MNullableType and not self.compiler.modelbuilder.toolcontext.opt_no_check_attr_isset.value then
+                       if not ret.is_c_primitive and not ret isa MNullableType and not self.compiler.modelbuilder.toolcontext.opt_no_check_attr_isset.value then
                                self.add("if (unlikely({res} == NULL)) \{")
                                self.add_abort("Uninitialized attribute {a.name}")
                                self.add("\}")
@@ -1408,7 +1693,11 @@ class SeparateCompilerVisitor
                self.require_declaration(a.const_color)
                if self.compiler.modelbuilder.toolcontext.opt_no_union_attribute.value then
                        var attr = "{recv}->attrs[{a.const_color}]"
-                       if mtype.ctype != "val*" then
+                       if mtype.is_tagged then
+                               # The attribute is not primitive, thus store it as tagged
+                               var tv = autobox(value, compiler.mainmodule.object_type)
+                               self.add("{attr} = {tv}; /* {a} on {recv.inspect} */")
+                       else if mtype.is_c_primitive then
                                assert mtype isa MClassType
                                # The attribute is primitive, thus we store it in a box
                                # The trick is to create the box the first time then resuse the box
@@ -1534,7 +1823,7 @@ class SeparateCompilerVisitor
                                self.add("count_type_test_resolved_{tag}++;")
                        end
                else
-                       self.add("PRINT_ERROR(\"NOT YET IMPLEMENTED: type_test(%s, {mtype}).\\n\", \"{value.inspect}\"); show_backtrace(1);")
+                       self.add("PRINT_ERROR(\"NOT YET IMPLEMENTED: type_test(%s, {mtype}).\\n\", \"{value.inspect}\"); fatal_exit(1);")
                end
 
                # check color is in table
@@ -1560,15 +1849,15 @@ class SeparateCompilerVisitor
        do
                var res = self.new_var(bool_type)
                # Swap values to be symetric
-               if value2.mtype.ctype != "val*" and value1.mtype.ctype == "val*" then
+               if value2.mtype.is_c_primitive and not value1.mtype.is_c_primitive then
                        var tmp = value1
                        value1 = value2
                        value2 = tmp
                end
-               if value1.mtype.ctype != "val*" then
+               if value1.mtype.is_c_primitive then
                        if value2.mtype == value1.mtype then
                                self.add("{res} = 1; /* is_same_type_test: compatible types {value1.mtype} vs. {value2.mtype} */")
-                       else if value2.mtype.ctype != "val*" then
+                       else if value2.mtype.is_c_primitive then
                                self.add("{res} = 0; /* is_same_type_test: incompatible types {value1.mtype} vs. {value2.mtype}*/")
                        else
                                var mtype1 = value1.mtype.as(MClassType)
@@ -1576,7 +1865,7 @@ class SeparateCompilerVisitor
                                self.add("{res} = ({value2} != NULL) && ({value2}->class == &class_{mtype1.c_name}); /* is_same_type_test */")
                        end
                else
-                       self.add("{res} = ({value1} == {value2}) || ({value1} != NULL && {value2} != NULL && {value1}->class == {value2}->class); /* is_same_type_test */")
+                       self.add("{res} = ({value1} == {value2}) || ({value1} != NULL && {value2} != NULL && {class_info(value1)} == {class_info(value2)}); /* is_same_type_test */")
                end
                return res
        end
@@ -1585,8 +1874,8 @@ class SeparateCompilerVisitor
        do
                var res = self.get_name("var_class_name")
                self.add_decl("const char* {res};")
-               if value.mtype.ctype == "val*" then
-                       self.add "{res} = {value} == NULL ? \"null\" : {value}->type->name;"
+               if not value.mtype.is_c_primitive then
+                       self.add "{res} = {value} == NULL ? \"null\" : {type_info(value)}->name;"
                else if value.mtype isa MClassType and value.mtype.as(MClassType).mclass.kind == extern_kind and
                        value.mtype.as(MClassType).name != "NativeString" then
                        self.add "{res} = \"{value.mtype.as(MClassType).mclass}\";"
@@ -1600,16 +1889,18 @@ class SeparateCompilerVisitor
        redef fun equal_test(value1, value2)
        do
                var res = self.new_var(bool_type)
-               if value2.mtype.ctype != "val*" and value1.mtype.ctype == "val*" then
+               if value2.mtype.is_c_primitive and not value1.mtype.is_c_primitive then
                        var tmp = value1
                        value1 = value2
                        value2 = tmp
                end
-               if value1.mtype.ctype != "val*" then
+               if value1.mtype.is_c_primitive then
                        if value2.mtype == value1.mtype then
                                self.add("{res} = {value1} == {value2};")
-                       else if value2.mtype.ctype != "val*" then
+                       else if value2.mtype.is_c_primitive then
                                self.add("{res} = 0; /* incompatible types {value1.mtype} vs. {value2.mtype}*/")
+                       else if value1.mtype.is_tagged then
+                               self.add("{res} = ({value2} != NULL) && ({self.autobox(value2, value1.mtype)} == {value1});")
                        else
                                var mtype1 = value1.mtype.as(MClassType)
                                self.require_declaration("class_{mtype1.c_name}")
@@ -1639,20 +1930,34 @@ class SeparateCompilerVisitor
 
                var incompatible = false
                var primitive
-               if t1.ctype != "val*" then
+               if t1.is_c_primitive then
                        primitive = t1
                        if t1 == t2 then
                                # No need to compare class
-                       else if t2.ctype != "val*" then
+                       else if t2.is_c_primitive then
                                incompatible = true
                        else if can_be_primitive(value2) then
+                               if t1.is_tagged then
+                                       self.add("{res} = {value1} == {value2};")
+                                       return res
+                               end
+                               if not compiler.modelbuilder.toolcontext.opt_no_tag_primitives.value then
+                                       test.add("(!{extract_tag(value2)})")
+                               end
                                test.add("{value1}->class == {value2}->class")
                        else
                                incompatible = true
                        end
-               else if t2.ctype != "val*" then
+               else if t2.is_c_primitive then
                        primitive = t2
                        if can_be_primitive(value1) then
+                               if t2.is_tagged then
+                                       self.add("{res} = {value1} == {value2};")
+                                       return res
+                               end
+                               if not compiler.modelbuilder.toolcontext.opt_no_tag_primitives.value then
+                                       test.add("(!{extract_tag(value1)})")
+                               end
                                test.add("{value1}->class == {value2}->class")
                        else
                                incompatible = true
@@ -1671,13 +1976,25 @@ class SeparateCompilerVisitor
                        end
                end
                if primitive != null then
+                       if primitive.is_tagged then
+                               self.add("{res} = {value1} == {value2};")
+                               return res
+                       end
                        test.add("((struct instance_{primitive.c_name}*){value1})->value == ((struct instance_{primitive.c_name}*){value2})->value")
                else if can_be_primitive(value1) and can_be_primitive(value2) then
+                       if not compiler.modelbuilder.toolcontext.opt_no_tag_primitives.value then
+                               test.add("(!{extract_tag(value1)}) && (!{extract_tag(value2)})")
+                       end
                        test.add("{value1}->class == {value2}->class")
                        var s = new Array[String]
                        for t, v in self.compiler.box_kinds do
+                               if t.mclass_type.is_tagged then continue
                                s.add "({value1}->class->box_kind == {v} && ((struct instance_{t.c_name}*){value1})->value == ((struct instance_{t.c_name}*){value2})->value)"
                        end
+                       if s.is_empty then
+                               self.add("{res} = {value1} == {value2};")
+                               return res
+                       end
                        test.add("({s.join(" || ")})")
                else
                        self.add("{res} = {value1} == {value2};")
@@ -1689,10 +2006,10 @@ class SeparateCompilerVisitor
 
        fun can_be_primitive(value: RuntimeVariable): Bool
        do
-               var t = value.mcasttype.as_notnullable
+               var t = value.mcasttype.undecorate
                if not t isa MClassType then return false
                var k = t.mclass.kind
-               return k == interface_kind or t.ctype != "val*"
+               return k == interface_kind or t.is_c_primitive
        end
 
        fun maybe_null(value: RuntimeVariable): Bool
@@ -1703,8 +2020,8 @@ class SeparateCompilerVisitor
 
        redef fun array_instance(array, elttype)
        do
-               var nclass = self.get_class("NativeArray")
-               var arrayclass = self.get_class("Array")
+               var nclass = mmodule.native_array_class
+               var arrayclass = mmodule.array_class
                var arraytype = arrayclass.get_mtype([elttype])
                var res = self.init_instance(arraytype)
                self.add("\{ /* {res} = array_instance Array[{elttype}] */")
@@ -1721,7 +2038,7 @@ class SeparateCompilerVisitor
 
        redef fun native_array_instance(elttype: MType, length: RuntimeVariable): RuntimeVariable
        do
-               var mtype = self.get_class("NativeArray").get_mtype([elttype])
+               var mtype = mmodule.native_array_type(elttype)
                self.require_declaration("NEW_{mtype.mclass.c_name}")
                assert mtype isa MGenericType
                var compiler = self.compiler
@@ -1741,10 +2058,13 @@ class SeparateCompilerVisitor
        redef fun native_array_def(pname, ret_type, arguments)
        do
                var elttype = arguments.first.mtype
-               var nclass = self.get_class("NativeArray")
+               var nclass = mmodule.native_array_class
                var recv = "((struct instance_{nclass.c_name}*){arguments[0]})->values"
                if pname == "[]" then
-                       self.ret(self.new_expr("{recv}[{arguments[1]}]", ret_type.as(not null)))
+                       # Because the objects are boxed, return the box to avoid unnecessary (or broken) unboxing/reboxing
+                       var res = self.new_expr("{recv}[{arguments[1]}]", compiler.mainmodule.object_type)
+                       res.mcasttype = ret_type.as(not null)
+                       self.ret(res)
                        return
                else if pname == "[]=" then
                        self.add("{recv}[{arguments[1]}]={arguments[2]};")
@@ -1759,12 +2079,20 @@ class SeparateCompilerVisitor
                end
        end
 
-       redef fun calloc_array(ret_type, arguments)
+       redef fun native_array_get(nat, i)
        do
-               var mclass = self.get_class("ArrayCapable")
-               var ft = mclass.mparameters.first
-               var res = self.native_array_instance(ft, arguments[1])
-               self.ret(res)
+               var nclass = mmodule.native_array_class
+               var recv = "((struct instance_{nclass.c_name}*){nat})->values"
+               # Because the objects are boxed, return the box to avoid unnecessary (or broken) unboxing/reboxing
+               var res = self.new_expr("{recv}[{i}]", compiler.mainmodule.object_type)
+               return res
+       end
+
+       redef fun native_array_set(nat, i, val)
+       do
+               var nclass = mmodule.native_array_class
+               var recv = "((struct instance_{nclass.c_name}*){nat})->values"
+               self.add("{recv}[{i}]={val};")
        end
 
        fun link_unresolved_type(mclassdef: MClassDef, mtype: MType) do
@@ -1877,7 +2205,7 @@ class SeparateRuntimeFunction
                for i in [0..called_signature.arity[ do
                        var mtype = called_signature.mparameters[i].mtype
                        if i == called_signature.vararg_rank then
-                               mtype = mmethoddef.mclassdef.mmodule.get_primitive_class("Array").get_mtype([mtype])
+                               mtype = mmethoddef.mclassdef.mmodule.array_type(mtype)
                        end
                        sig.append(", {mtype.ctype} p{i}")
                end
@@ -1885,6 +2213,12 @@ class SeparateRuntimeFunction
                return sig.to_s
        end
 
+       # The C type for the function pointer.
+       var c_funptrtype: String is lazy do return "{c_ret}(*){c_sig}"
+
+       # The arguments, as generated by `compile_to_c`
+       private var arguments: Array[RuntimeVariable] is noinit
+
        redef fun compile_to_c(compiler)
        do
                var mmethoddef = self.mmethoddef
@@ -1910,7 +2244,7 @@ class SeparateRuntimeFunction
                for i in [0..msignature.arity[ do
                        var mtype = msignature.mparameters[i].mtype
                        if i == msignature.vararg_rank then
-                               mtype = v.get_class("Array").get_mtype([mtype])
+                               mtype = v.mmodule.array_type(mtype)
                        end
                        comment.append(", {mtype}")
                        var argvar = new RuntimeVariable("p{i}", mtype, mtype)
@@ -1921,6 +2255,7 @@ class SeparateRuntimeFunction
                        comment.append(": {ret}")
                end
                compiler.provide_declaration(self.c_name, "{sig};")
+               self.arguments = arguments.to_a
 
                v.add_decl("/* method {self} for {comment} */")
                v.add_decl("{sig} \{")
@@ -1946,6 +2281,56 @@ class SeparateRuntimeFunction
                v.add("\}")
                compiler.names[self.c_name] = "{mmethoddef.full_name} ({mmethoddef.location.file.filename}:{mmethoddef.location.line_start})"
        end
+
+       # Compile the trampolines used to implement late-binding.
+       #
+       # See `opt_trampoline_call`.
+       fun compile_trampolines(compiler: SeparateCompiler)
+       do
+               var recv = self.mmethoddef.mclassdef.bound_mtype
+               var selfvar = arguments.first
+               var ret = called_signature.return_mtype
+
+               if mmethoddef.is_intro and not recv.is_c_primitive then
+                       var m = mmethoddef.mproperty
+                       var n2 = "CALL_" + m.const_color
+                       compiler.provide_declaration(n2, "{c_ret} {n2}{c_sig};")
+                       var v2 = compiler.new_visitor
+                       v2.add "{c_ret} {n2}{c_sig} \{"
+                       v2.require_declaration(m.const_color)
+                       var call = "(({c_funptrtype})({v2.class_info(selfvar)}->vft[{m.const_color}]))({arguments.join(", ")});"
+                       if ret != null then
+                               v2.add "return {call}"
+                       else
+                               v2.add call
+                       end
+
+                       v2.add "\}"
+
+               end
+               if mmethoddef.has_supercall and not recv.is_c_primitive then
+                       var m = mmethoddef
+                       var n2 = "CALL_" + m.const_color
+                       compiler.provide_declaration(n2, "{c_ret} {n2}{c_sig};")
+                       var v2 = compiler.new_visitor
+                       v2.add "{c_ret} {n2}{c_sig} \{"
+                       v2.require_declaration(m.const_color)
+                       var call = "(({c_funptrtype})({v2.class_info(selfvar)}->vft[{m.const_color}]))({arguments.join(", ")});"
+                       if ret != null then
+                               v2.add "return {call}"
+                       else
+                               v2.add call
+                       end
+
+                       v2.add "\}"
+               end
+       end
+end
+
+redef class MType
+       # Are values of `self` tagged?
+       # If false, it means that the type is not primitive, or is boxed.
+       var is_tagged = false
 end
 
 redef class MEntity
@@ -1971,3 +2356,14 @@ redef class AMethPropdef
                return super
        end
 end
+
+redef class AAttrPropdef
+       redef fun init_expr(v, recv)
+       do
+               super
+               if is_lazy and v.compiler.modelbuilder.toolcontext.opt_no_union_attribute.value then
+                       var guard = self.mlazypropdef.mproperty
+                       v.write_attribute(guard, recv, v.bool_instance(false))
+               end
+       end
+end