sepcomp: `compute_resolution_tables` group by classes
[nit.git] / src / compiler / separate_compiler.nit
index a33c098..5b5975f 100644 (file)
@@ -29,6 +29,8 @@ 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 (link-boost)", "--colors-are-symbols")
@@ -65,6 +67,7 @@ redef class ToolContext
                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)
+               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)
@@ -163,6 +166,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
@@ -217,6 +221,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
@@ -447,14 +456,9 @@ class SeparateCompiler
                # 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 poset = poset_from_mtypes(live_types, live_cast_types)
                var colorer = new POSetColorer[MType]
                colorer.colorize(poset)
                type_ids = colorer.ids
@@ -462,20 +466,42 @@ class SeparateCompiler
                type_tables = build_type_tables(poset)
 
                # 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
        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.as_notnullable.as(MClassType).mclass
+                       mtypes_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
+
+               var casttypes_by_class = new MultiHashMap[MClass, MType]
+               for e in cast_types do
+                       var c = e.as_notnullable.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
@@ -501,29 +527,33 @@ 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)
-
+       # 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)
@@ -548,9 +578,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]
@@ -763,8 +794,6 @@ 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
@@ -778,7 +807,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 */")
@@ -800,6 +830,8 @@ class SeparateCompiler
                if mtype.ctype != "val*" 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;")
@@ -905,18 +937,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
@@ -1076,8 +1168,30 @@ class SeparateCompilerVisitor
                else if value.mtype.ctype == "val*" and mtype.ctype == "val*" then
                        return value
                else if value.mtype.ctype == "val*" 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
+                       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
@@ -1085,7 +1199,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}")
@@ -1099,7 +1213,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
@@ -1125,7 +1239,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}")
@@ -1140,11 +1254,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 value.mtype.ctype == "val*"
+               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 value.mtype.ctype == "val*" 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 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)
@@ -1317,14 +1462,14 @@ class SeparateCompilerVisitor
                                self.add "{ress}{callsym}({ss}); /* {mmethod} on {arguments.first.inspect}*/"
                        else
                                self.require_declaration(const_color)
-                               self.add "{ress}(({runtime_function.c_funptrtype})({arguments.first}->class->vft[{const_color}]))({ss}); /* {mmethod} on {arguments.first.inspect}*/"
+                               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})({arguments.first}->class->vft[{const_color}]))({ss}); /* {mmethod} on {arguments.first.inspect}*/"
+                       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 "\}"
@@ -1334,7 +1479,7 @@ class SeparateCompilerVisitor
                        self.add "{ress}{callsym}({ss}); /* {mmethod} on {arguments.first.inspect}*/"
                else
                        self.require_declaration(const_color)
-                       self.add "{ress}(({runtime_function.c_funptrtype})({arguments.first}->class->vft[{const_color}]))({ss}); /* {mmethod} on {arguments.first.inspect}*/"
+                       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
@@ -1654,7 +1799,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
@@ -1696,7 +1841,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
@@ -1706,7 +1851,7 @@ class SeparateCompilerVisitor
                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;"
+                       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}\";"
@@ -1730,6 +1875,8 @@ class SeparateCompilerVisitor
                                self.add("{res} = {value1} == {value2};")
                        else if value2.mtype.ctype != "val*" 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}")
@@ -1766,6 +1913,13 @@ class SeparateCompilerVisitor
                        else if t2.ctype != "val*" 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
@@ -1773,6 +1927,13 @@ class SeparateCompilerVisitor
                else if t2.ctype != "val*" 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
@@ -1791,13 +1952,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};")
@@ -2122,6 +2295,12 @@ class SeparateRuntimeFunction
        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
        var const_color: String is lazy do return "COLOR_{c_name}"
 end