X-Git-Url: http://nitlanguage.org diff --git a/src/compiler/separate_compiler.nit b/src/compiler/separate_compiler.nit index 9273c45..a51ef09 100644 --- a/src/compiler/separate_compiler.nit +++ b/src/compiler/separate_compiler.nit @@ -29,11 +29,15 @@ 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") # --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 @@ -45,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 @@ -61,7 +67,8 @@ 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(opt_colors_are_symbols, opt_trampoline_call, opt_substitute_monomorph, opt_link_boost) + 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) @@ -159,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 @@ -213,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 @@ -590,7 +603,7 @@ class SeparateCompiler # Process all introduced methods and compile some linking information (if needed) fun link_mmethods do - if not modelbuilder.toolcontext.opt_substitute_monomorph.value then return + 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 @@ -613,6 +626,11 @@ class SeparateCompiler 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. @@ -791,6 +809,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;") @@ -908,6 +928,58 @@ class SeparateCompiler 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}" + 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 @@ -1067,8 +1139,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 @@ -1076,7 +1170,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}") @@ -1090,7 +1184,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 @@ -1116,7 +1210,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}") @@ -1131,11 +1225,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) @@ -1147,25 +1272,37 @@ 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 @@ -1279,20 +1416,41 @@ class SeparateCompilerVisitor end var const_color = mentity.const_color - var call - if not compiler.modelbuilder.toolcontext.opt_trampoline_call.value then - self.require_declaration(const_color) - call = "(({runtime_function.c_funptrtype})({arguments.first}->class->vft[{const_color}]))({ss}) /* {mmethod} on {arguments.first.inspect}*/" + var ress + if res != null then + ress = "{res} = " else + 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) - call = "{callsym}({ss}) /* {mmethod} on {arguments.first.inspect}*/" - end - - if res != null then - self.add("{res} = {call};") + 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.add("{call};") + 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 @@ -1612,7 +1770,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 @@ -1654,7 +1812,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 @@ -1664,7 +1822,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}\";" @@ -1688,6 +1846,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}") @@ -1724,6 +1884,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 @@ -1731,6 +1898,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 @@ -1749,13 +1923,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};") @@ -1822,7 +2008,10 @@ class SeparateCompilerVisitor var nclass = self.get_class("NativeArray") 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]};") @@ -2077,6 +2266,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