nitc: renamed --no-check-initialization in --no-check-attr-isset to follow the new...
[nit.git] / src / abstract_compiler.nit
index 44aa5ab..dae3d88 100644 (file)
@@ -21,8 +21,8 @@ import literal
 import typing
 import auto_super_init
 import frontend
-import common_ffi
 import platform
+import c_tools
 
 # Add compiling options
 redef class ToolContext
@@ -42,8 +42,8 @@ redef class ToolContext
        var opt_no_shortcut_range: OptionBool = new OptionBool("Always insantiate a range and its iterator on 'for' loops", "--no-shortcut-range")
        # --no-check-covariance
        var opt_no_check_covariance: OptionBool = new OptionBool("Disable type tests of covariant parameters (dangerous)", "--no-check-covariance")
-       # --no-check-initialization
-       var opt_no_check_initialization: OptionBool = new OptionBool("Disable isset tests at the end of constructors (dangerous)", "--no-check-initialization")
+       # --no-check-attr-isset
+       var opt_no_check_attr_isset: OptionBool = new OptionBool("Disable isset tests before each attribute access (dangerous)", "--no-check-attr-isset")
        # --no-check-assert
        var opt_no_check_assert: OptionBool = new OptionBool("Disable the evaluation of explicit 'assert' and 'as' (dangerous)", "--no-check-assert")
        # --no-check-autocast
@@ -52,10 +52,10 @@ 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")
-       # --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")
+       # --invocation-metrics
+       var opt_invocation_metrics: OptionBool = new OptionBool("Enable static and dynamic count of all method invocations", "--invocation-metrics")
+       # --stacktrace
+       var opt_stacktrace: OptionString = new OptionString("Control the generation of stack traces", "--stacktrace")
        # --no-gcc-directives
        var opt_no_gcc_directive = new OptionArray("Disable a advanced gcc directives for optimization", "--no-gcc-directive")
 
@@ -63,25 +63,30 @@ redef class ToolContext
        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_no_check_covariance, self.opt_no_check_attr_isset, 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.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
 
-redef class ModelBuilder
-       redef init(model, toolcontext)
+       redef fun process_options(args)
        do
-               if toolcontext.opt_no_stacktrace.value and toolcontext.opt_stacktrace.value then
-                       print "Cannot use --nit-stacktrace when --no-stacktrace is activated"
+               super
+
+               var st = opt_stacktrace.value
+               if st == null or st == "none" or st == "libunwind" or st == "nitstack" then
+                       # Fine, do nothing
+               else if st == "auto" then
+                       # Default just unset
+                       opt_stacktrace.value = null
+               else
+                       print "Error: unknown value `{st}` for --stacktrace. Use `none`, `libunwind`, `nitstack` or `auto`."
                        exit(1)
                end
-
-               super
        end
+end
 
+redef class ModelBuilder
        # The compilation directory
        var compile_dir: String
 
@@ -123,22 +128,19 @@ class MakefileToolchain
        # The list is initially set with :
        #   * the toolcontext --cc-path option
        #   * the NIT_CC_PATH environment variable
-       #   * some heuristics including the NIT_DIR environment variable and the progname of the process
+       #   * `toolcontext.nit_dir`
        # Path can be added (or removed) by the client
        var cc_paths = new Array[String]
 
        protected fun gather_cc_paths
        do
                # Look for the the Nit clib path
-               var path_env = "NIT_DIR".environ
-               if not path_env.is_empty then
+               var path_env = toolcontext.nit_dir
+               if path_env != null then
                        var libname = "{path_env}/clib"
                        if libname.file_exists then cc_paths.add(libname)
                end
 
-               var libname = "{sys.program_name.dirname}/../clib"
-               if libname.file_exists then cc_paths.add(libname.simplify_path)
-
                if cc_paths.is_empty then
                        toolcontext.error(null, "Cannot determine the nit clib path. define envvar NIT_DIR.")
                end
@@ -192,7 +194,7 @@ class MakefileToolchain
 
        fun write_files(compiler: AbstractCompiler, compile_dir: String, cfiles: Array[String])
        do
-               if self.toolcontext.opt_stacktrace.value then compiler.build_c_to_nit_bindings
+               if self.toolcontext.opt_stacktrace.value == "nitstack" then compiler.build_c_to_nit_bindings
 
                # Add gc_choser.h to aditionnal bodies
                var gc_chooser = new ExternCFile("gc_chooser.c", "-DWITH_LIBGC")
@@ -202,11 +204,8 @@ class MakefileToolchain
 
                # FFI
                var m2m = toolcontext.modelbuilder.mmodule2nmodule
-               for m in compiler.mainmodule.in_importation.greaters do if m2m.keys.has(m) then
-                       var amodule = m2m[m]
-                       if m.uses_ffi or amodule.uses_legacy_ni then
-                               compiler.finalize_ffi_for_module(amodule)
-                       end
+               for m in compiler.mainmodule.in_importation.greaters do
+                       compiler.finalize_ffi_for_module(m)
                end
 
                # Copy original .[ch] files to compile_dir
@@ -305,12 +304,13 @@ class MakefileToolchain
 
                var linker_options = new HashSet[String]
                var m2m = toolcontext.modelbuilder.mmodule2nmodule
-               for m in mainmodule.in_importation.greaters do if m2m.keys.has(m) then
-                       var amod = m2m[m]
-                       linker_options.add(amod.c_linker_options)
+               for m in mainmodule.in_importation.greaters do
+                       var libs = m.collect_linker_libs
+                       if libs != null then linker_options.add_all(libs)
                end
 
-               if not toolcontext.opt_no_stacktrace.value then linker_options.add("-lunwind")
+               var ost = toolcontext.opt_stacktrace.value
+               if ost == "libunwind" or ost == "nitstack" then linker_options.add("-lunwind")
 
                makefile.write("CC = ccache cc\nCFLAGS = -g -O2\nCINCL = {cc_includes}\nLDFLAGS ?= \nLDLIBS  ?= -lm -lgc {linker_options.join(" ")}\n\n")
                makefile.write("all: {outpath}\n\n")
@@ -320,29 +320,35 @@ class MakefileToolchain
                # Compile each generated file
                for f in cfiles do
                        var o = f.strip_extension(".c") + ".o"
-                       makefile.write("{o}: {f}\n\t$(CC) $(CFLAGS) $(CINCL) -D NONITCNI -c -o {o} {f}\n\n")
+                       makefile.write("{o}: {f}\n\t$(CC) $(CFLAGS) $(CINCL) -c -o {o} {f}\n\n")
                        ofiles.add(o)
                        dep_rules.add(o)
                end
 
+               var java_files = new Array[ExternFile]
+
                # Compile each required extern body into a specific .o
                for f in compiler.extern_bodies do
-                       if f isa ExternCFile then
-                               var basename = f.filename.basename(".c")
-                               var o = "{basename}.extern.o"
-                               var ff = f.filename.basename("")
-                               makefile.write("{o}: {ff}\n\t$(CC) $(CFLAGS) -D NONITCNI {f.cflags} -c -o {o} {ff}\n\n")
-                               ofiles.add(o)
-                               dep_rules.add(o)
-                       else
-                               var o = f.makefile_rule_name
-                               var ff = f.filename.basename("")
-                               makefile.write("{o}: {ff}\n")
-                               makefile.write("\t{f.makefile_rule_content}\n\n")
-                               dep_rules.add(f.makefile_rule_name)
+                       var o = f.makefile_rule_name
+                       var ff = f.filename.basename("")
+                       makefile.write("{o}: {ff}\n")
+                       makefile.write("\t{f.makefile_rule_content}\n\n")
+                       dep_rules.add(f.makefile_rule_name)
 
-                               if f isa ExternCppFile then ofiles.add(o)
-                       end
+                       if f.compiles_to_o_file then ofiles.add(o)
+                       if f.add_to_jar then java_files.add(f)
+               end
+               
+               if not java_files.is_empty then
+                       var jar_file = "{outpath}.jar"
+
+                       var class_files_array = new Array[String]
+                       for f in java_files do class_files_array.add(f.makefile_rule_name)
+                       var class_files = class_files_array.join(" ")
+
+                       makefile.write("{jar_file}: {class_files}\n")
+                       makefile.write("\tjar cf {jar_file} {class_files}\n\n")
+                       dep_rules.add jar_file
                end
 
                # Link edition
@@ -438,30 +444,40 @@ abstract class AbstractCompiler
        do
                var compile_dir = modelbuilder.compile_dir
 
-               var stream = new OFStream.open("{compile_dir}/C_fun_names")
-               stream.write("%\{\n#include \"c_functions_hash.h\"\n%\}\n")
-               stream.write("%define lookup-function-name get_nit_name\n")
-               stream.write("struct C_Nit_Names;\n")
-               stream.write("%%\n")
-               stream.write("####\n")
+               var stream = new OFStream.open("{compile_dir}/c_functions_hash.c")
+               stream.write("#include <string.h>\n")
+               stream.write("#include <stdlib.h>\n")
+               stream.write("#include \"c_functions_hash.h\"\n")
+               stream.write("typedef struct C_Nit_Names\{char* name; char* nit_name;\}C_Nit_Names;\n")
+               stream.write("const char* get_nit_name(register const char* procproc, register unsigned int len)\{\n")
+               stream.write("char* procname = malloc(len+1);")
+               stream.write("memcpy(procname, procproc, len);")
+               stream.write("procname[len] = '\\0';")
+               stream.write("static const C_Nit_Names map[{names.length}] = \{\n")
                for i in names.keys do
+                       stream.write("\{\"")
                        stream.write(i)
-                       stream.write(",\t\"")
+                       stream.write("\",\"")
                        stream.write(names[i])
-                       stream.write("\"\n")
-               end
-               stream.write("####\n")
-               stream.write("%%\n")
+                       stream.write("\"\},\n")
+               end
+               stream.write("\};\n")
+               stream.write("int i;")
+               stream.write("for(i = 0; i < {names.length}; i++)\{")
+               stream.write("if(strcmp(procname,map[i].name) == 0)\{")
+               stream.write("free(procname);")
+               stream.write("return map[i].nit_name;")
+               stream.write("\}")
+               stream.write("\}")
+               stream.write("free(procname);")
+               stream.write("return NULL;")
+               stream.write("\}\n")
                stream.close
 
                stream = new OFStream.open("{compile_dir}/c_functions_hash.h")
-               stream.write("typedef struct C_Nit_Names\{char* name; char* nit_name;\}C_Nit_Names;\n")
-               stream.write("const struct C_Nit_Names* get_nit_name(register const char *str, register unsigned int len);\n")
+               stream.write("const char* get_nit_name(register const char* procname, register unsigned int len);\n")
                stream.close
 
-               var x = new Process("gperf","{compile_dir}/C_fun_names","-t","-7","--output-file={compile_dir}/c_functions_hash.c","-C")
-               x.wait
-
                extern_bodies.add(new ExternCFile("{compile_dir}/c_functions_hash.c", ""))
        end
 
@@ -519,13 +535,20 @@ abstract class AbstractCompiler
        fun compile_main_function
        do
                var v = self.new_visitor
-               if modelbuilder.toolcontext.opt_stacktrace.value then
-                       v.add_decl("#include \"c_functions_hash.h\"")
-               end
                v.add_decl("#include <signal.h>")
-               if not modelbuilder.toolcontext.opt_no_stacktrace.value then
+               var ost = modelbuilder.toolcontext.opt_stacktrace.value
+
+               if ost == null then
+                       ost = "nitstack"
+                       modelbuilder.toolcontext.opt_stacktrace.value = ost
+               end
+
+               if ost == "nitstack" or ost == "libunwind" then
                        v.add_decl("#define UNW_LOCAL_ONLY")
                        v.add_decl("#include <libunwind.h>")
+                       if ost == "nitstack" then
+                               v.add_decl("#include \"c_functions_hash.h\"")
+                       end
                end
                v.add_decl("int glob_argc;")
                v.add_decl("char **glob_argv;")
@@ -542,13 +565,22 @@ 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);")
                v.add_decl("\}")
 
                v.add_decl("void show_backtrace (int signo) \{")
-               if not modelbuilder.toolcontext.opt_no_stacktrace.value then
+               if ost == "nitstack" or ost == "libunwind" then
                        v.add_decl("char* opt = getenv(\"NIT_NO_STACK\");")
                        v.add_decl("unw_cursor_t cursor;")
                        v.add_decl("if(opt==NULL)\{")
@@ -562,10 +594,10 @@ abstract class AbstractCompiler
                        v.add_decl("printf(\"-------------------------------------------------\\n\");")
                        v.add_decl("while (unw_step(&cursor) > 0) \{")
                        v.add_decl("    unw_get_proc_name(&cursor, procname, 100, &ip);")
-                       if modelbuilder.toolcontext.opt_stacktrace.value then
-                       v.add_decl("    const C_Nit_Names* recv = get_nit_name(procname, strlen(procname));")
-                       v.add_decl("    if (recv != 0)\{")
-                       v.add_decl("            printf(\"` %s\\n\", recv->nit_name);")
+                       if ost == "nitstack" then
+                       v.add_decl("    const char* recv = get_nit_name(procname, strlen(procname));")
+                       v.add_decl("    if (recv != NULL)\{")
+                       v.add_decl("            printf(\"` %s\\n\", recv);")
                        v.add_decl("    \}else\{")
                        v.add_decl("            printf(\"` %s\\n\", procname);")
                        v.add_decl("    \}")
@@ -633,6 +665,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
@@ -730,6 +770,8 @@ abstract class AbstractCompiler
                end
        end
 
+       fun finalize_ffi_for_module(mmodule: MModule) do mmodule.finalize_ffi(self)
+
        # Division facility
        # Avoid division by zero by returning the string "n/a"
        fun div(a,b:Int):String
@@ -737,13 +779,6 @@ abstract class AbstractCompiler
                if b == 0 then return "n/a"
                return ((a*10000/b).to_f / 100.0).to_precision(2)
        end
-
-       fun finalize_ffi_for_module(nmodule: AModule)
-       do
-               var visitor = new_visitor
-               nmodule.finalize_ffi(visitor, modelbuilder)
-               nmodule.finalize_nitni(visitor)
-       end
 end
 
 # A file unit (may be more than one file if
@@ -1629,12 +1664,6 @@ redef class AInternMethPropdef
                        else if pname == "unary -" then
                                v.ret(v.new_expr("-{arguments[0]}", ret.as(not null)))
                                return
-                       else if pname == "succ" then
-                               v.ret(v.new_expr("{arguments[0]}+1", ret.as(not null)))
-                               return
-                       else if pname == "prec" then
-                               v.ret(v.new_expr("{arguments[0]}-1", ret.as(not null)))
-                               return
                        else if pname == "*" then
                                v.ret(v.new_expr("{arguments[0]} * {arguments[1]}", ret.as(not null)))
                                return
@@ -1683,10 +1712,10 @@ redef class AInternMethPropdef
                        else if pname == "object_id" then
                                v.ret(v.new_expr("(long){arguments.first}", ret.as(not null)))
                                return
-                       else if pname == "+" then
+                       else if pname == "successor" then
                                v.ret(v.new_expr("{arguments[0]} + {arguments[1]}", ret.as(not null)))
                                return
-                       else if pname == "-" then
+                       else if pname == "predecessor" then
                                v.ret(v.new_expr("{arguments[0]} - {arguments[1]}", ret.as(not null)))
                                return
                        else if pname == "==" then
@@ -1696,12 +1725,6 @@ redef class AInternMethPropdef
                                var res = v.equal_test(arguments[0], arguments[1])
                                v.ret(v.new_expr("!{res}", ret.as(not null)))
                                return
-                       else if pname == "succ" then
-                               v.ret(v.new_expr("{arguments[0]}+1", ret.as(not null)))
-                               return
-                       else if pname == "prec" then
-                               v.ret(v.new_expr("{arguments[0]}-1", ret.as(not null)))
-                               return
                        else if pname == "<" then
                                v.ret(v.new_expr("{arguments[0]} < {arguments[1]}", ret.as(not null)))
                                return
@@ -2211,29 +2234,29 @@ redef class AForExpr
                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
@@ -2243,7 +2266,7 @@ redef class AForExpr
                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
@@ -2386,7 +2409,7 @@ redef class ACrangeExpr
                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
@@ -2398,7 +2421,7 @@ redef class AOrangeExpr
                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
@@ -2644,15 +2667,11 @@ redef class MModule
                return properties_cache[mclass]
        end
        private var properties_cache: Map[MClass, Set[MProperty]] = new HashMap[MClass, Set[MProperty]]
-end
-
-redef class AModule
-       # Does this module use the legacy native interface?
-       fun uses_legacy_ni: Bool is abstract
 
-       # Write FFI results to file
-       fun finalize_ffi(v: AbstractCompilerVisitor, modelbuilder: ModelBuilder) is abstract
+       # Write FFI and nitni results to file
+       fun finalize_ffi(c: AbstractCompiler) do end
 
-       # Write nitni results to file
-       fun finalize_nitni(v: AbstractCompilerVisitor) is abstract
+       # Give requided addinional system libraries (as given to LD_LIBS)
+       # Note: can return null instead of an empty set
+       fun collect_linker_libs: nullable Set[String] do return null
 end