Merge: Nitgs optims
authorJean Privat <jean@pryen.org>
Thu, 3 Apr 2014 20:06:32 +0000 (16:06 -0400)
committerJean Privat <jean@pryen.org>
Thu, 3 Apr 2014 20:06:32 +0000 (16:06 -0400)
Some optimizations on the nitg-s side (low level).

Some use the new call-graph mechanism.

Pull-Request: #372
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>

benchmarks/bench_engines.sh
src/abstract_compiler.nit
src/model/model.nit
src/modelize_property.nit
src/separate_compiler.nit

index d894cc0..a97c796 100755 (executable)
@@ -225,8 +225,9 @@ function bench_nitg-g_options()
        fi
 
        for opt in "$@"; do
-               prepare_res "$name$opt.dat" "$opt" "nitg-g with option $opt"
-               run_compiler "nitg-g$opt" ./nitg --global $opt
+               ot=${opt// /+}
+               prepare_res "$name$ot.dat" "$opt" "nitg-g with option $opt"
+               run_compiler "nitg-g$ot" ./nitg --global $opt
        done
 
        plot "$name.gnu"
@@ -251,15 +252,16 @@ function bench_nitg-s_options()
        fi
 
        for opt in "$@"; do
-               prepare_res "$name$opt.dat" "$opt" "nitg-s with option $opt"
-               run_compiler "nitg-s$opt" ./nitg --separate $opt
+               ot=${opt// /+}
+               prepare_res "$name-$ot.dat" "$opt" "nitg-s with option $opt"
+               run_compiler "nitg-s$ot" ./nitg --separate $opt
        done
 
        plot "$name.gnu"
 }
-bench_nitg-s_options "slower" --hardening --no-inline-intern --no-union-attribute --no-shortcut-equal --no-shortcut-range
+bench_nitg-s_options "slower" --hardening --no-inline-intern --no-union-attribute --no-shortcut-equal --no-shortcut-range "--no-gcc-directive likely" "--no-gcc-directive noreturn"
 bench_nitg-s_options "nocheck" --no-check-covariance --no-check-initialization --no-check-assert --no-check-autocast --no-check-other
-bench_nitg-s_options "faster" --inline-coloring-numbers
+bench_nitg-s_options "faster" --inline-coloring-numbers --inline-some-methods --direct-call-monomorph "--inline-some-methods --direct-call-monomorph"
 bench_nitg-s_options "typing" NOALL --bm-typing --phand-typing
 
 function bench_nitg-e_options()
@@ -279,8 +281,9 @@ function bench_nitg-e_options()
        fi
 
        for opt in "$@"; do
-               prepare_res "$name$opt.dat" "$opt" "nitg-e with option $opt"
-               run_compiler "nitg-e$opt" ./nitg --erasure $opt
+               ot=${opt// /+}
+               prepare_res "$name$ot.dat" "$opt" "nitg-e with option $opt"
+               run_compiler "nitg-e$ot" ./nitg --erasure $opt
        done
 
        plot "$name.gnu"
index 0f854ba..90eb876 100644 (file)
@@ -52,19 +52,24 @@ redef class ToolContext
        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 @@ abstract class AbstractCompiler
                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 @@ abstract class AbstractCompiler
                        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 @@ abstract class AbstractCompiler
                        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 @@ abstract class AbstractCompilerVisitor
 
                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 @@ abstract class AbstractCompilerVisitor
        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
@@ -2234,7 +2274,7 @@ redef class AAssertExpr
                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
@@ -2419,7 +2459,7 @@ redef class AAsNotnullExpr
                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
index 5c76a56..bebfa5c 100644 (file)
@@ -1822,8 +1822,11 @@ class MMethodDef
        # 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
index c7f8e8b..9667d77 100644 (file)
@@ -554,6 +554,7 @@ redef class AMethPropdef
                msignature = new MSignature(mparameters, ret_type)
                mpropdef.msignature = msignature
                mpropdef.is_abstract = self isa ADeferredMethPropdef
+               mpropdef.is_intern = self isa AInternMethPropdef
        end
 
        redef fun check_signature(modelbuilder, nclassdef)
index 2352646..92cd821 100644 (file)
@@ -31,7 +31,11 @@ redef class ToolContext
        # --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 @@ 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_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 @@ redef class ModelBuilder
                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 @@ class SeparateCompiler
                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 @@ class SeparateCompilerVisitor
                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 @@ class SeparateCompilerVisitor
                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 @@ class SeparateCompilerVisitor
                                        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 @@ class SeparateCompilerVisitor
                                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 @@ class SeparateCompilerVisitor
                                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 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 @@ class SeparateCompilerVisitor
                        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 @@ class SeparateCompilerVisitor
                        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 @@ class SeparateCompilerVisitor
 
                        # 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 @@ class SeparateCompilerVisitor
 
                        # 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