From: Jean Privat Date: Thu, 3 Apr 2014 20:06:32 +0000 (-0400) Subject: Merge: Nitgs optims X-Git-Tag: v0.6.6~132 X-Git-Url: http://nitlanguage.org?hp=-c Merge: Nitgs optims Some optimizations on the nitg-s side (low level). Some use the new call-graph mechanism. Pull-Request: #372 Reviewed-by: Alexis Laferrière --- 09a5a53a9b7bd2aa30bfa8350fddeea448df4204 diff --combined src/abstract_compiler.nit index 0f854ba,1beb99d..90eb876 --- a/src/abstract_compiler.nit +++ b/src/abstract_compiler.nit @@@ -52,19 -52,24 +52,24 @@@ redef class ToolContex var opt_no_check_other: OptionBool = new OptionBool("Disable implicit tests: unset attribute, null receiver (dangerous)", "--no-check-other") # --typing-test-metrics var opt_typing_test_metrics: OptionBool = new OptionBool("Enable static and dynamic count of all type tests", "--typing-test-metrics") + # --invocation-metrics + var opt_invocation_metrics: OptionBool = new OptionBool("Enable static and dynamic count of all method invocations", "--invocation-metrics") # --no-stacktrace var opt_no_stacktrace: OptionBool = new OptionBool("Disables libunwind and generation of C stack traces (can be problematic when compiling to targets such as Android or NaCl)", "--no-stacktrace") # --stack-trace-C-to-Nit-name-binding var opt_stacktrace: OptionBool = new OptionBool("Enables the use of gperf to bind C to Nit function names when encountering a Stack trace at runtime", "--nit-stacktrace") + # --no-gcc-directives + var opt_no_gcc_directive = new OptionArray("Disable a advanced gcc directives for optimization", "--no-gcc-directive") redef init do super self.option_context.add_option(self.opt_output, self.opt_no_cc, self.opt_make_flags, self.opt_compile_dir, self.opt_hardening, self.opt_no_shortcut_range) self.option_context.add_option(self.opt_no_check_covariance, self.opt_no_check_initialization, self.opt_no_check_assert, self.opt_no_check_autocast, self.opt_no_check_other) - self.option_context.add_option(self.opt_typing_test_metrics) + self.option_context.add_option(self.opt_typing_test_metrics, self.opt_invocation_metrics) self.option_context.add_option(self.opt_stacktrace) self.option_context.add_option(self.opt_no_stacktrace) + self.option_context.add_option(self.opt_no_gcc_directive) end end @@@ -475,8 -480,26 +480,26 @@@ abstract class AbstractCompile compile_header_structs compile_nitni_structs - # Signal handler function prototype - self.header.add_decl("void show_backtrace(int);") + var gccd_disable = modelbuilder.toolcontext.opt_no_gcc_directive.value + if gccd_disable.has("noreturn") or gccd_disable.has("all") then + # Signal handler function prototype + self.header.add_decl("void show_backtrace(int);") + else + self.header.add_decl("void show_backtrace(int) __attribute__ ((noreturn));") + end + + if gccd_disable.has("likely") or gccd_disable.has("all") then + self.header.add_decl("#define likely(x) (x)") + self.header.add_decl("#define unlikely(x) (x)") + else if gccd_disable.has("correct-likely") then + # invert the `likely` definition + # Used by masochists to bench the worst case + self.header.add_decl("#define likely(x) __builtin_expect((x),0)") + self.header.add_decl("#define unlikely(x) __builtin_expect((x),1)") + else + self.header.add_decl("#define likely(x) __builtin_expect((x),1)") + self.header.add_decl("#define unlikely(x) __builtin_expect((x),0)") + end # Global variable used by intern methods self.header.add_decl("extern int glob_argc;") @@@ -521,6 -544,15 +544,15 @@@ end end + if self.modelbuilder.toolcontext.opt_invocation_metrics.value then + v.add_decl("long count_invoke_by_tables;") + v.add_decl("long count_invoke_by_direct;") + v.add_decl("long count_invoke_by_inline;") + v.compiler.header.add_decl("extern long count_invoke_by_tables;") + v.compiler.header.add_decl("extern long count_invoke_by_direct;") + v.compiler.header.add_decl("extern long count_invoke_by_inline;") + end + v.add_decl("void sig_handler(int signo)\{") v.add_decl("printf(\"Caught signal : %s\\n\", strsignal(signo));") v.add_decl("show_backtrace(signo);") @@@ -612,6 -644,14 +644,14 @@@ end end + if self.modelbuilder.toolcontext.opt_invocation_metrics.value then + v.add_decl("long count_invoke_total;") + v.add("count_invoke_total = count_invoke_by_tables + count_invoke_by_direct + count_invoke_by_inline;") + v.add("printf(\"# dynamic count_invocation: total %ld\\n\", count_invoke_total);") + v.add("printf(\"by table: %ld (%.2f%%)\\n\", count_invoke_by_tables, 100.0*count_invoke_by_tables/count_invoke_total);") + v.add("printf(\"direct: %ld (%.2f%%)\\n\", count_invoke_by_direct, 100.0*count_invoke_by_direct/count_invoke_total);") + v.add("printf(\"inlined: %ld (%.2f%%)\\n\", count_invoke_by_inline, 100.0*count_invoke_by_inline/count_invoke_total);") + end v.add("return 0;") v.add("\}") end @@@ -933,7 -973,7 +973,7 @@@ abstract class AbstractCompilerVisito var maybenull = recv.mcasttype isa MNullableType or recv.mcasttype isa MNullType if maybenull then - self.add("if ({recv} == NULL) \{") + self.add("if (unlikely({recv} == NULL)) \{") self.add_abort("Receiver is null") self.add("\}") end @@@ -1152,7 -1192,7 +1192,7 @@@ fun add_cast(value: RuntimeVariable, mtype: MType, tag: String) do var res = self.type_test(value, mtype, tag) - self.add("if (!{res}) \{") + self.add("if (unlikely(!{res})) \{") var cn = self.class_name_string(value) self.add("fprintf(stderr, \"Runtime error: Cast failed. Expected `%s`, got `%s`\", \"{mtype.to_s.escape_to_c}\", {cn});") self.add_raw_abort @@@ -2190,29 -2230,29 +2230,29 @@@ redef class AForExp var cl = v.expr(self.n_expr, null) var it_meth = self.method_iterator assert it_meth != null - var it = v.send(it_meth, [cl]) + var it = v.compile_callsite(it_meth, [cl]) assert it != null v.add("for(;;) \{") var isok_meth = self.method_is_ok assert isok_meth != null - var ok = v.send(isok_meth, [it]) + var ok = v.compile_callsite(isok_meth, [it]) assert ok != null v.add("if(!{ok}) break;") if self.variables.length == 1 then var item_meth = self.method_item assert item_meth != null - var i = v.send(item_meth, [it]) + var i = v.compile_callsite(item_meth, [it]) assert i != null v.assign(v.variable(variables.first), i) else if self.variables.length == 2 then var key_meth = self.method_key assert key_meth != null - var i = v.send(key_meth, [it]) + var i = v.compile_callsite(key_meth, [it]) assert i != null v.assign(v.variable(variables[0]), i) var item_meth = self.method_item assert item_meth != null - i = v.send(item_meth, [it]) + i = v.compile_callsite(item_meth, [it]) assert i != null v.assign(v.variable(variables[1]), i) else @@@ -2222,7 -2262,7 +2262,7 @@@ v.add("CONTINUE_{v.escapemark_name(escapemark)}: (void)0;") var next_meth = self.method_next assert next_meth != null - v.send(next_meth, [it]) + v.compile_callsite(next_meth, [it]) v.add("\}") v.add("BREAK_{v.escapemark_name(escapemark)}: (void)0;") end @@@ -2234,7 -2274,7 +2274,7 @@@ redef class AAssertExp if v.compiler.modelbuilder.toolcontext.opt_no_check_assert.value then return var cond = v.expr_bool(self.n_expr) - v.add("if (!{cond}) \{") + v.add("if (unlikely(!{cond})) \{") v.stmt(self.n_else) var nid = self.n_id if nid != null then @@@ -2365,7 -2405,7 +2405,7 @@@ redef class ACrangeExp var i2 = v.expr(self.n_expr2, null) var mtype = self.mtype.as(MClassType) var res = v.init_instance(mtype) - var it = v.send(v.get_property("init", res.mtype), [res, i1, i2]) + var it = v.compile_callsite(init_callsite.as(not null), [res, i1, i2]) return res end end @@@ -2377,7 -2417,7 +2417,7 @@@ redef class AOrangeExp var i2 = v.expr(self.n_expr2, null) var mtype = self.mtype.as(MClassType) var res = v.init_instance(mtype) - var it = v.send(v.get_property("without_last", res.mtype), [res, i1, i2]) + var it = v.compile_callsite(init_callsite.as(not null), [res, i1, i2]) return res end end @@@ -2419,7 -2459,7 +2459,7 @@@ redef class AAsNotnullExp var i = v.expr(self.n_expr, null) if v.compiler.modelbuilder.toolcontext.opt_no_check_assert.value then return i - v.add("if ({i} == NULL) \{") + v.add("if (unlikely({i} == NULL)) \{") v.add_abort("Cast failed") v.add("\}") return i diff --combined src/model/model.nit index 5c76a56,85d281e..bebfa5c --- a/src/model/model.nit +++ b/src/model/model.nit @@@ -1418,7 -1418,7 +1418,7 @@@ class MSignatur redef fun to_s do - var b = new Buffer + var b = new FlatBuffer if not mparameters.is_empty then b.append("(") for i in [0..mparameters.length[ do @@@ -1822,8 -1822,11 +1822,11 @@@ class MMethodDe # The signature attached to the property definition var msignature: nullable MSignature writable = null - # The the method definition abstract? + # Is the method definition abstract? var is_abstract: Bool writable = false + + # Is the method definition intern? + var is_intern writable = false end # A local definition of an attribute diff --combined src/separate_compiler.nit index 2352646,96978f0..92cd821 --- a/src/separate_compiler.nit +++ b/src/separate_compiler.nit @@@ -31,7 -31,11 +31,11 @@@ redef class ToolContex # --no-shortcut-equate var opt_no_shortcut_equate: OptionBool = new OptionBool("Always call == in a polymorphic way", "--no-shortcut-equal") # --inline-coloring-numbers - var opt_inline_coloring_numbers: OptionBool = new OptionBool("Inline colors and ids", "--inline-coloring-numbers") + var opt_inline_coloring_numbers: OptionBool = new OptionBool("Inline colors and ids (semi-global)", "--inline-coloring-numbers") + # --inline-some-methods + var opt_inline_some_methods: OptionBool = new OptionBool("Allow the separate compiler to inline some methods (semi-global)", "--inline-some-methods") + # --direct-call-monomorph + var opt_direct_call_monomorph: OptionBool = new OptionBool("Allow the separate compiler to direct call monomorph sites (semi-global)", "--direct-call-monomorph") # --use-naive-coloring var opt_bm_typing: OptionBool = new OptionBool("Colorize items incrementaly, used to simulate binary matrix typing", "--bm-typing") # --use-mod-perfect-hashing @@@ -48,7 -52,7 +52,7 @@@ 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_inline_coloring_numbers) + self.option_context.add_option(self.opt_inline_coloring_numbers, opt_inline_some_methods, opt_direct_call_monomorph) self.option_context.add_option(self.opt_bm_typing) self.option_context.add_option(self.opt_phmod_typing) self.option_context.add_option(self.opt_phand_typing) @@@ -106,6 -110,13 +110,13 @@@ redef class ModelBuilde self.toolcontext.info("*** END GENERATING C: {time1-time0} ***", 2) write_and_make(compiler) end + + # Count number of invocations by VFT + private var nb_invok_by_tables = 0 + # Count number of invocations by direct call + private var nb_invok_by_direct = 0 + # Count number of invocations by inlining + private var nb_invok_by_inline = 0 end # Singleton that store the knowledge about the separate compilation process @@@ -865,6 -876,14 +876,14 @@@ class SeparateCompile if self.modelbuilder.toolcontext.opt_tables_metrics.value then display_sizes end + + var tc = self.modelbuilder.toolcontext + tc.info("# implementation of method invocation",2) + var nb_invok_total = modelbuilder.nb_invok_by_tables + modelbuilder.nb_invok_by_direct + modelbuilder.nb_invok_by_inline + tc.info("total number of invocations: {nb_invok_total}",2) + tc.info("invocations by VFT send: {modelbuilder.nb_invok_by_tables} ({div(modelbuilder.nb_invok_by_tables,nb_invok_total)}%)",2) + tc.info("invocations by direct call: {modelbuilder.nb_invok_by_direct} ({div(modelbuilder.nb_invok_by_direct,nb_invok_total)}%)",2) + tc.info("invocations by inlining: {modelbuilder.nb_invok_by_inline} ({div(modelbuilder.nb_invok_by_inline,nb_invok_total)}%)",2) end fun display_sizes @@@ -989,6 -1008,29 +1008,29 @@@ class SeparateCompilerVisito end end + redef fun compile_callsite(callsite, args) + do + var rta = compiler.runtime_type_analysis + var recv = args.first.mtype + if compiler.modelbuilder.toolcontext.opt_direct_call_monomorph.value and rta != null then + var tgs = rta.live_targets(callsite) + if tgs.length == 1 then + # DIRECT CALL + var mmethod = callsite.mproperty + self.varargize(mmethod.intro, mmethod.intro.msignature.as(not null), args) + 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 + end + end + return super + end redef fun send(mmethod, arguments) do self.varargize(mmethod.intro, mmethod.intro.msignature.as(not null), arguments) @@@ -1006,45 -1048,27 +1048,27 @@@ return table_send(mmethod, arguments, mmethod.const_color) end - private fun table_send(mmethod: MMethod, arguments: Array[RuntimeVariable], const_color: String): nullable RuntimeVariable + # Handel common special cases before doing the effective method invocation + # This methods handle the `==` and `!=` methods and the case of the null receiver. + # Note: a { is open in the generated C, that enclose and protect the effective method invocation. + # Client must not forget to close the } after them. + # + # The value returned is the result of the common special cases. + # If not null, client must compine it with the result of their own effective method invocation. + # + # If `before_send` can shortcut the whole message sending, a dummy `if(0){` + # is generated to cancel the effective method invocation that will follow + # TODO: find a better approach + private fun before_send(mmethod: MMethod, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do - assert arguments.length == mmethod.intro.msignature.arity + 1 else debug("Invalid arity for {mmethod}. {arguments.length} arguments given.") - - 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 mmethod.is_new then - ret = arguments.first.mtype - res = self.new_var(ret) - else if ret == null then - res = null - else - res = self.new_var(ret) - end - - var s = new FlatBuffer - var ss = new FlatBuffer - + var res: nullable RuntimeVariable = null var recv = arguments.first - s.append("val*") - ss.append("{recv}") - for i in [0..msignature.arity[ do - var a = arguments[i+1] - var t = msignature.mparameters[i].mtype - 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 consider_null = not self.compiler.modelbuilder.toolcontext.opt_no_check_other.value or mmethod.name == "==" or mmethod.name == "!=" var maybenull = recv.mcasttype isa MNullableType and consider_null if maybenull then self.add("if ({recv} == NULL) \{") if mmethod.name == "==" then - assert res != null + res = self.new_var(bool_type) var arg = arguments[1] if arg.mcasttype isa MNullableType then self.add("{res} = ({arg} == NULL);") @@@ -1054,7 -1078,7 +1078,7 @@@ self.add("{res} = 0; /* {arg.inspect} cannot be null */") end else if mmethod.name == "!=" then - assert res != null + res = self.new_var(bool_type) var arg = arguments[1] if arg.mcasttype isa MNullableType then self.add("{res} = ({arg} != NULL);") @@@ -1067,9 -1091,11 +1091,11 @@@ self.add_abort("Receiver is null") end self.add("\} else \{") + else + self.add("\{") end if not self.compiler.modelbuilder.toolcontext.opt_no_shortcut_equate.value and (mmethod.name == "==" or mmethod.name == "!=") then - assert res != null + if res == null then res = self.new_var(bool_type) # Recv is not null, thus is arg is, it is easy to conclude (and respect the invariants) var arg = arguments[1] if arg.mcasttype isa MNullType then @@@ -1078,12 -1104,51 +1104,51 @@@ else self.add("{res} = 1; /* arg is null and recv is not */") end - if maybenull then - self.add("\}") - end - return res + self.add("\}") # closes the null case + self.add("if (0) \{") # what follow is useless, CC will drop it end end + return res + end + + private fun table_send(mmethod: MMethod, arguments: Array[RuntimeVariable], const_color: String): nullable RuntimeVariable + do + compiler.modelbuilder.nb_invok_by_tables += 1 + if compiler.modelbuilder.toolcontext.opt_invocation_metrics.value then add("count_invoke_by_tables++;") + + assert arguments.length == mmethod.intro.msignature.arity + 1 else debug("Invalid arity for {mmethod}. {arguments.length} arguments given.") + var recv = arguments.first + + var res0 = before_send(mmethod, arguments) + + 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 mmethod.is_new then + ret = arguments.first.mtype + res = self.new_var(ret) + else if ret == null then + res = null + else + res = self.new_var(ret) + end + - var s = new Buffer - var ss = new Buffer ++ 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] + var t = msignature.mparameters[i].mtype + 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 @@@ -1096,10 -1161,14 +1161,14 @@@ self.add("{call};") end - if maybenull then - self.add("\}") + if res0 != null then + assert res != null + assign(res0,res) + res = res0 end + self.add("\}") # closes the null case + return res end @@@ -1119,28 -1188,31 +1188,31 @@@ res = self.new_var(ret) end - if self.compiler.modelbuilder.mpropdef2npropdef.has_key(mmethoddef) and - self.compiler.modelbuilder.mpropdef2npropdef[mmethoddef] isa AInternMethPropdef and - not compiler.modelbuilder.toolcontext.opt_no_inline_intern.value then + if (mmethoddef.is_intern and not compiler.modelbuilder.toolcontext.opt_no_inline_intern.value) or + (compiler.modelbuilder.toolcontext.opt_inline_some_methods.value and mmethoddef.can_inline(self)) then + compiler.modelbuilder.nb_invok_by_inline += 1 + if compiler.modelbuilder.toolcontext.opt_invocation_metrics.value then add("count_invoke_by_inline++;") var frame = new Frame(self, mmethoddef, recvtype, arguments) frame.returnlabel = self.get_name("RET_LABEL") frame.returnvar = res var old_frame = self.frame self.frame = frame - self.add("\{ /* Inline {mmethoddef} ({arguments.join(",")}) */") + self.add("\{ /* Inline {mmethoddef} ({arguments.join(",")}) on {arguments.first.inspect} */") mmethoddef.compile_inside_to_c(self, arguments) self.add("{frame.returnlabel.as(not null)}:(void)0;") self.add("\}") self.frame = old_frame return res end + compiler.modelbuilder.nb_invok_by_direct += 1 + if compiler.modelbuilder.toolcontext.opt_invocation_metrics.value then add("count_invoke_by_direct++;") # Autobox arguments self.adapt_signature(mmethoddef, arguments) self.require_declaration(mmethoddef.c_name) if res == null then - self.add("{mmethoddef.c_name}({arguments.join(", ")});") + self.add("{mmethoddef.c_name}({arguments.join(", ")}); /* Direct call {mmethoddef} on {arguments.first.inspect}*/") return null else self.add("{res} = {mmethoddef.c_name}({arguments.join(", ")});") @@@ -1231,7 -1303,7 +1303,7 @@@ # Check for Uninitialized attribute if not ret isa MNullableType and not self.compiler.modelbuilder.toolcontext.opt_no_check_initialization.value then - self.add("if ({res} == NULL) \{") + self.add("if (unlikely({res} == NULL)) \{") self.add_abort("Uninitialized attribute {a.name}") self.add("\}") end @@@ -1245,7 -1317,7 +1317,7 @@@ # Check for Uninitialized attribute if ret.ctype == "val*" and not ret isa MNullableType and not self.compiler.modelbuilder.toolcontext.opt_no_check_initialization.value then - self.add("if ({res} == NULL) \{") + self.add("if (unlikely({res} == NULL)) \{") self.add_abort("Uninitialized attribute {a.name}") self.add("\}") end @@@ -1695,8 -1767,8 +1767,8 @@@ class SeparateRuntimeFunctio var msignature = mmethoddef.msignature.resolve_for(mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.mmodule, true) - var sig = new Buffer - var comment = new Buffer + var sig = new FlatBuffer + var comment = new FlatBuffer var ret = msignature.return_mtype if ret != null then sig.append("{ret.ctype} ") @@@ -1768,8 -1840,8 +1840,8 @@@ class VirtualRuntimeFunctio var frame = new Frame(v, mmethoddef, recv, arguments) v.frame = frame - var sig = new Buffer - var comment = new Buffer + var sig = new FlatBuffer + var comment = new FlatBuffer # Because the function is virtual, the signature must match the one of the original class var intromclassdef = self.mmethoddef.mproperty.intro.mclassdef