Merge: Use linker symbols to encode colors
authorJean Privat <jean@pryen.org>
Tue, 13 Jan 2015 01:29:02 +0000 (20:29 -0500)
committerJean Privat <jean@pryen.org>
Tue, 13 Jan 2015 01:29:02 +0000 (20:29 -0500)
Genuine constant static variables are used to store the colors used in OO mechanisms.
This makes the compiler program slower since additional indirections are required to get the values.
It also produces a little bit larger executables since static memory has to store the colors.

This PR introduce a trick user in the original PRM that uses linker symbols to encode the colors.
It is not completely portable so it must be activated with the option `--colors-are-symbols`

For numbers (with the traditional nitc/nitc/nitc)
before: 0m7.544s
after: 0m7.228s (so -4%)

Pull-Request: #1093
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>

1  2 
src/compiler/abstract_compiler.nit
src/compiler/android_platform.nit
src/compiler/separate_compiler.nit

@@@ -120,20 -120,16 +120,16 @@@ redef class ModelBuilde
        # Simple indirection to `Toolchain::write_and_make`
        protected fun write_and_make(compiler: AbstractCompiler)
        do
-               var platform = compiler.mainmodule.target_platform
-               var toolchain
-               if platform == null then
-                       toolchain = new MakefileToolchain(toolcontext)
-               else
-                       toolchain = platform.toolchain(toolcontext)
-               end
+               var platform = compiler.target_platform
+               var toolchain = platform.toolchain(toolcontext)
                compile_dir = toolchain.compile_dir
                toolchain.write_and_make compiler
        end
  end
  
  redef class Platform
-       fun toolchain(toolcontext: ToolContext): Toolchain is abstract
+       # The specific tool-chain associated to the platform
+       fun toolchain(toolcontext: ToolContext): Toolchain do return new MakefileToolchain(toolcontext)
  end
  
  class Toolchain
@@@ -189,10 -185,10 +185,10 @@@ class MakefileToolchai
  
        fun write_files(compiler: AbstractCompiler, compile_dir: String, cfiles: Array[String])
        do
-               var platform = compiler.mainmodule.target_platform
-               if self.toolcontext.opt_stacktrace.value == "nitstack" and (platform == null or platform.supports_libunwind) then compiler.build_c_to_nit_bindings
+               var platform = compiler.target_platform
+               if self.toolcontext.opt_stacktrace.value == "nitstack" and platform.supports_libunwind then compiler.build_c_to_nit_bindings
                var cc_opt_with_libgc = "-DWITH_LIBGC"
-               if platform != null and not platform.supports_libgc then cc_opt_with_libgc = ""
+               if not platform.supports_libgc then cc_opt_with_libgc = ""
  
                # Add gc_choser.h to aditionnal bodies
                var gc_chooser = new ExternCFile("gc_chooser.c", cc_opt_with_libgc)
        fun write_makefile(compiler: AbstractCompiler, compile_dir: String, cfiles: Array[String])
        do
                var mainmodule = compiler.mainmodule
-               var platform = compiler.mainmodule.target_platform
+               var platform = compiler.target_platform
  
                var outname = outfile(mainmodule)
  
                makefile.write("CC = ccache cc\nCXX = ccache c++\nCFLAGS = -g -O2 -Wno-unused-value -Wno-switch -Wno-attributes\nCINCL =\nLDFLAGS ?= \nLDLIBS  ?= -lm {linker_options.join(" ")}\n\n")
  
                var ost = toolcontext.opt_stacktrace.value
-               if (ost == "libunwind" or ost == "nitstack") and (platform == null or platform.supports_libunwind) then makefile.write("NEED_LIBUNWIND := YesPlease\n")
+               if (ost == "libunwind" or ost == "nitstack") and platform.supports_libunwind then makefile.write("NEED_LIBUNWIND := YesPlease\n")
  
                # Dynamic adaptations
                # While `platform` enable complex toolchains, they are statically applied
                        dep_rules.add(o)
                end
  
+               # Generate linker script, if any
+               if not compiler.linker_script.is_empty then
+                       var linker_script_path = "{compile_dir}/linker_script"
+                       ofiles.add "linker_script"
+                       var f = new OFStream.open(linker_script_path)
+                       for l in compiler.linker_script do
+                               f.write l
+                               f.write "\n"
+                       end
+                       f.close
+               end
                var java_files = new Array[ExternFile]
  
                var pkgconfigs = new Array[String]
@@@ -476,9 -484,13 +484,13 @@@ abstract class AbstractCompile
        # Is hardening asked? (see --hardening)
        fun hardening: Bool do return self.modelbuilder.toolcontext.opt_hardening.value
  
+       # The targeted specific platform
+       var target_platform: Platform is noinit
        init
        do
                self.realmainmodule = mainmodule
+               target_platform = mainmodule.target_platform or else new Platform
        end
  
        # Do the full code generation of the program `mainmodule`
        # Where global declaration are stored (the main .h)
        var header: CodeWriter is writable, noinit
  
+       # Additionnal linker script for `ld`.
+       # Mainly used to do specific link-time symbol resolution
+       var linker_script = new Array[String]
        # Provide a declaration that can be requested (before or latter) by a visitor
        fun provide_declaration(key: String, s: String)
        do
@@@ -681,11 -697,11 +697,11 @@@ extern void nitni_global_ref_decr( stru
                var v = self.new_visitor
                v.add_decl("#include <signal.h>")
                var ost = modelbuilder.toolcontext.opt_stacktrace.value
-               var platform = mainmodule.target_platform
+               var platform = target_platform
  
-               if platform != null and not platform.supports_libunwind then ost = "none"
+               if not platform.supports_libunwind then ost = "none"
  
-               var no_main = (platform != null and platform.no_main) or modelbuilder.toolcontext.opt_no_main.value
+               var no_main = platform.no_main or modelbuilder.toolcontext.opt_no_main.value
  
                if ost == "nitstack" or ost == "libunwind" then
                        v.add_decl("#define UNW_LOCAL_ONLY")
@@@ -1040,8 -1056,8 +1056,8 @@@ abstract class AbstractCompilerVisito
        # The current visited AST node
        var current_node: nullable ANode = null is writable
  
 -      # The current `Frame`
 -      var frame: nullable Frame = null is writable
 +      # The current `StaticFrame`
 +      var frame: nullable StaticFrame = null is writable
  
        # Alias for self.compiler.mainmodule.object_type
        fun object_type: MClassType do return self.compiler.mainmodule.object_type
@@@ -1670,8 -1686,8 +1686,8 @@@ class RuntimeVariabl
        end
  end
  
 -# A frame correspond to a visited property in a `GlobalCompilerVisitor`
 -class Frame
 +# The static context of a visited property in a `AbstractCompilerVisitor`
 +class StaticFrame
  
        type VISITOR: AbstractCompilerVisitor
  
@@@ -2270,7 -2286,7 +2286,7 @@@ redef class AAttrPropde
                var oldnode = v.current_node
                v.current_node = self
                var old_frame = v.frame
 -              var frame = new Frame(v, self.mpropdef.as(not null), recv.mcasttype.as_notnullable.as(MClassType), [recv])
 +              var frame = new StaticFrame(v, self.mpropdef.as(not null), recv.mcasttype.as_notnullable.as(MClassType), [recv])
                v.frame = frame
  
                var value
                var oldnode = v.current_node
                v.current_node = self
                var old_frame = v.frame
 -              var frame = new Frame(v, self.mpropdef.as(not null), recv.mtype.as(MClassType), [recv])
 +              var frame = new StaticFrame(v, self.mpropdef.as(not null), recv.mtype.as(MClassType), [recv])
                v.frame = frame
                # Force read to check the initialization
                v.read_attribute(self.mpropdef.mproperty, recv)
@@@ -36,6 -36,8 +36,8 @@@ class AndroidPlatfor
  
        redef fun supports_libunwind do return false
  
+       redef fun supports_linker_script do return false
        redef fun toolchain(toolcontext) do return new AndroidToolchain(toolcontext)
  end
  
@@@ -174,8 -176,7 +176,8 @@@ $(call import-module,android/native_app
      <application
                android:label="@string/app_name"
                android:hasCode="true"
 -              android:debuggable="{{{not release}}}">
 +              android:debuggable="{{{not release}}}"
 +              {{{icon_declaration}}}>
  
          <!-- Our activity is the built-in NativeActivity framework class.
               This will take care of integrating with our NDK code. -->
                # Move the apk to the target
                var outname = outfile(compiler.mainmodule)
  
 -              var src_apk_suffix
                if release then
 -                      src_apk_suffix = "release-unsigned"
 -              else src_apk_suffix = "debug"
 +                      var apk_path = "{android_project_root}/bin/{compiler.mainmodule.name}-release-unsigned.apk"
  
 -              toolcontext.exec_and_check(["mv", "{android_project_root}/bin/{compiler.mainmodule.name}-{src_apk_suffix}.apk", outname], "Android project error")
 +                      # Sign APK
 +                      var keystore_path= "KEYSTORE".environ
 +                      var key_alias= "KEY_ALIAS".environ
 +                      var tsa_server= "TSA_SERVER".environ
 +
 +                      if key_alias.is_empty then
 +                              toolcontext.fatal_error(null,
 +                                      "Fatal Error: the environment variable `KEY_ALIAS` must be set to use the `--release` option on Android projects.")
 +                      end
 +
 +                      args = ["jarsigner", "-sigalg", "MD5withRSA", "-digestalg", "SHA1", apk_path, key_alias]
 +
 +                      ## Use a custom keystore
 +                      if not keystore_path.is_empty then args.add_all(["-keystore", keystore_path])
 +
 +                      ## Use a TSA server
 +                      if not tsa_server.is_empty then args.add_all(["-tsa", tsa_server])
 +
 +                      toolcontext.exec_and_check(args, "Android project error")
 +
 +                      # Clean output file
 +                      if outname.to_path.exists then outname.to_path.delete
 +
 +                      # Align APK
 +                      args = ["zipalign", "4", apk_path, outname]
 +                      toolcontext.exec_and_check(args, "Android project error")
 +              else
 +                      # Move to the expected output path
 +                      args = ["mv", "{android_project_root}/bin/{compiler.mainmodule.name}-debug.apk", outname]
 +                      toolcontext.exec_and_check(args, "Android project error")
 +              end
        end
  end
  
@@@ -29,6 -29,9 +29,9 @@@ redef class ToolContex
        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")
+       # --colors-are-symbols
+       var opt_colors_are_symbols = new OptionBool("Store colors as symbols (faster)", "--colors-are-symbols")
        # --inline-coloring-numbers
        var opt_inline_coloring_numbers = new OptionBool("Inline colors and ids (semi-global)", "--inline-coloring-numbers")
        # --inline-some-methods
@@@ -50,7 -53,7 +53,7 @@@
                self.option_context.add_option(self.opt_separate)
                self.option_context.add_option(self.opt_no_inline_intern)
                self.option_context.add_option(self.opt_no_union_attribute)
-               self.option_context.add_option(self.opt_no_shortcut_equate)
+               self.option_context.add_option(self.opt_no_shortcut_equate, opt_colors_are_symbols)
                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)
@@@ -252,27 -255,21 +255,21 @@@ class SeparateCompile
  
        fun compile_color_const(v: SeparateCompilerVisitor, m: Object, color: Int) do
                if color_consts_done.has(m) then return
-               if m isa MProperty then
-                       if modelbuilder.toolcontext.opt_inline_coloring_numbers.value then
-                               self.provide_declaration(m.const_color, "#define {m.const_color} {color}")
-                       else
-                               self.provide_declaration(m.const_color, "extern const int {m.const_color};")
-                               v.add("const int {m.const_color} = {color};")
-                       end
-               else if m isa MPropDef then
+               if m isa MEntity then
                        if modelbuilder.toolcontext.opt_inline_coloring_numbers.value then
                                self.provide_declaration(m.const_color, "#define {m.const_color} {color}")
-                       else
+                       else if not modelbuilder.toolcontext.opt_colors_are_symbols.value or not v.compiler.target_platform.supports_linker_script then
                                self.provide_declaration(m.const_color, "extern const int {m.const_color};")
                                v.add("const int {m.const_color} = {color};")
-                       end
-               else if m isa MType then
-                       if modelbuilder.toolcontext.opt_inline_coloring_numbers.value then
-                               self.provide_declaration(m.const_color, "#define {m.const_color} {color}")
                        else
-                               self.provide_declaration(m.const_color, "extern const int {m.const_color};")
-                               v.add("const int {m.const_color} = {color};")
+                               # The color 'C' is the ``address'' of a false static variable 'XC'
+                               self.provide_declaration(m.const_color, "#define {m.const_color} ((long)&X{m.const_color})\nextern const void X{m.const_color};")
+                               if color == -1 then color = 0 # Symbols cannot be negative, so just use 0 for dead things
+                               # Teach the linker that the address of 'XC' is `color`.
+                               linker_script.add("X{m.const_color} = {color};")
                        end
+               else
+                       abort
                end
                color_consts_done.add(m)
        end
@@@ -1248,7 -1245,7 +1245,7 @@@ class SeparateCompilerVisito
                        (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)
 +                      var frame = new StaticFrame(self, mmethoddef, recvtype, arguments)
                        frame.returnlabel = self.get_name("RET_LABEL")
                        frame.returnvar = res
                        var old_frame = self.frame
                # of the method (ie recv) if the static type is unresolved
                # This is more complex than usual because the unresolved type must not be resolved
                # with the current receiver (ie self).
 -              # Therefore to isolate the resolution from self, a local Frame is created.
 +              # Therefore to isolate the resolution from self, a local StaticFrame is created.
                # One can see this implementation as an inlined method of the receiver whose only
                # job is to allocate the array
                var old_frame = self.frame
 -              var frame = new Frame(self, mpropdef, mpropdef.mclassdef.bound_mtype, [recv])
 +              var frame = new StaticFrame(self, mpropdef, mpropdef.mclassdef.bound_mtype, [recv])
                self.frame = frame
                #print "required Array[{elttype}] for recv {recv.inspect}. bound=Array[{self.resolve_for(elttype, recv)}]. selfvar={frame.arguments.first.inspect}"
                var res = self.array_instance(varargs, elttype)
@@@ -1820,7 -1817,7 +1817,7 @@@ class SeparateRuntimeFunctio
                var v = compiler.new_visitor
                var selfvar = new RuntimeVariable("self", recv, recv)
                var arguments = new Array[RuntimeVariable]
 -              var frame = new Frame(v, mmethoddef, recv, arguments)
 +              var frame = new StaticFrame(v, mmethoddef, recv, arguments)
                v.frame = frame
  
                var msignature = mmethoddef.msignature.resolve_for(mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.mmodule, true)
@@@ -1892,7 -1889,7 +1889,7 @@@ class VirtualRuntimeFunctio
                var v = compiler.new_visitor
                var selfvar = new RuntimeVariable("self", v.object_type, recv)
                var arguments = new Array[RuntimeVariable]
 -              var frame = new Frame(v, mmethoddef, recv, arguments)
 +              var frame = new StaticFrame(v, mmethoddef, recv, arguments)
                v.frame = frame
  
                var sig = new FlatBuffer
        redef fun call(v, arguments) do abort
  end
  
- redef class MType
-       fun const_color: String do return "COLOR_{c_name}"
+ redef class MEntity
+       var const_color: String is lazy do return "COLOR_{c_name}"
  end
  
  interface PropertyLayoutElement end
  
  redef class MProperty
        super PropertyLayoutElement
-       fun const_color: String do return "COLOR_{c_name}"
  end
  
  redef class MPropDef
        super PropertyLayoutElement
-       fun const_color: String do return "COLOR_{c_name}"
  end
  
  redef class AMethPropdef