Merge: Safe call operator
[nit.git] / src / compiler / abstract_compiler.nit
index 9ec830a..c74933c 100644 (file)
@@ -24,6 +24,8 @@ import c_tools
 private import annotation
 import mixin
 import counter
+import pkgconfig
+private import explain_assert_api
 
 # Add compiling options
 redef class ToolContext
@@ -31,10 +33,14 @@ redef class ToolContext
        var opt_output = new OptionString("Filename of the generated executable", "-o", "--output")
        # --dir
        var opt_dir = new OptionString("Output directory", "--dir")
+       # --run
+       var opt_run = new OptionBool("Execute the binary after the compilation", "--run")
        # --no-cc
        var opt_no_cc = new OptionBool("Do not invoke the C compiler", "--no-cc")
        # --no-main
        var opt_no_main = new OptionBool("Do not generate main entry point", "--no-main")
+       # --shared-lib
+       var opt_shared_lib = new OptionBool("Compile to a native shared library", "--shared-lib")
        # --make-flags
        var opt_make_flags = new OptionString("Additional options to the `make` command", "--make-flags")
        # --max-c-lines
@@ -71,11 +77,13 @@ redef class ToolContext
        var opt_release = new OptionBool("Compile in release mode and finalize application", "--release")
        # -g
        var opt_debug = new OptionBool("Compile in debug mode (no C-side optimization)", "-g", "--debug")
+       # --trace
+       var opt_trace = new OptionBool("Compile with lttng's instrumentation", "--trace")
 
        redef init
        do
                super
-               self.option_context.add_option(self.opt_output, self.opt_dir, self.opt_no_cc, self.opt_no_main, self.opt_make_flags, self.opt_compile_dir, self.opt_hardening)
+               self.option_context.add_option(self.opt_output, self.opt_dir, self.opt_run, self.opt_no_cc, self.opt_no_main, self.opt_shared_lib, self.opt_make_flags, self.opt_compile_dir, self.opt_hardening)
                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_null, self.opt_no_check_all)
                self.option_context.add_option(self.opt_typing_test_metrics, self.opt_invocation_metrics, self.opt_isset_checks_metrics)
                self.option_context.add_option(self.opt_no_stacktrace)
@@ -83,8 +91,10 @@ redef class ToolContext
                self.option_context.add_option(self.opt_release)
                self.option_context.add_option(self.opt_max_c_lines, self.opt_group_c_files)
                self.option_context.add_option(self.opt_debug)
+               self.option_context.add_option(self.opt_trace)
 
                opt_no_main.hidden = true
+               opt_shared_lib.hidden = true
        end
 
        redef fun process_options(args)
@@ -186,6 +196,8 @@ class MakefileToolchain
                var time1 = get_time
                self.toolcontext.info("*** END WRITING C: {time1-time0} ***", 2)
 
+               toolcontext.check_errors
+
                # Execute the Makefile
 
                if self.toolcontext.opt_no_cc.value then return
@@ -199,6 +211,14 @@ class MakefileToolchain
                        sys.system("rm -r -- '{root_compile_dir.escape_to_sh}/'")
                end
 
+               if toolcontext.opt_run.value then
+                       var mainmodule = compiler.mainmodule
+                       var out = outfile(mainmodule)
+                       var cmd = ["."/out]
+                       cmd.append toolcontext.option_context.rest
+                       toolcontext.exec_and_check(cmd, "--run")
+               end
+
                time1 = get_time
                self.toolcontext.info("*** END COMPILING C: {time1-time0} ***", 2)
        end
@@ -219,6 +239,16 @@ class MakefileToolchain
                compiler.files_to_copy.add "{clib}/gc_chooser.c"
                compiler.files_to_copy.add "{clib}/gc_chooser.h"
 
+               # Add lttng traces provider to external bodies
+               if toolcontext.opt_trace.value then
+                       #-I. is there in order to make the TRACEPOINT_INCLUDE directive in clib/traces.h refer to the directory in which gcc was invoked.
+                       var traces = new ExternCFile("traces.c", "-I.")
+                       traces.pkgconfigs.add "lttng-ust"
+                       compiler.extern_bodies.add(traces)
+                       compiler.files_to_copy.add "{clib}/traces.c"
+                       compiler.files_to_copy.add "{clib}/traces.h"
+               end
+
                # FFI
                for m in compiler.mainmodule.in_importation.greaters do
                        compiler.finalize_ffi_for_module(m)
@@ -350,14 +380,27 @@ class MakefileToolchain
                var debug = toolcontext.opt_debug.value
 
                makefile.write """
-CC ?= ccache cc
-CXX ?= ccache c++
+ifeq ($(origin CC), default)
+        CC = ccache cc
+endif
+ifeq ($(origin CXX), default)
+        CXX = ccache c++
+endif
 CFLAGS ?= -g {{{if not debug then "-O2" else ""}}} -Wno-unused-value -Wno-switch -Wno-attributes -Wno-trigraphs
 CINCL =
 LDFLAGS ?=
 LDLIBS  ?= -lm {{{linker_options.join(" ")}}}
 \n"""
 
+               if self.toolcontext.opt_trace.value then makefile.write "LDLIBS += -llttng-ust -ldl\n"
+
+               if toolcontext.opt_shared_lib.value then
+                       makefile.write """
+CFLAGS += -fPIC
+LDFLAGS += -shared -Wl,-soname,{{{outname}}}
+"""
+               end
+
                makefile.write "\n# SPECIAL CONFIGURATION FLAGS\n"
                if platform.supports_libunwind then
                        if toolcontext.opt_no_stacktrace.value then
@@ -422,6 +465,13 @@ ifneq ($(findstring MINGW64,$(uname_S)),)
        CFLAGS += -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast
 endif
 
+# Add the compilation dir to the Java CLASSPATH
+ifeq ($(CLASSPATH),)
+       CLASSPATH := .
+else
+       CLASSPATH := $(CLASSPATH):.
+endif
+
 """
 
                makefile.write("all: {outpath}\n")
@@ -452,14 +502,19 @@ endif
                        f.close
                end
 
-               var java_files = new Array[ExternFile]
-
+               # pkg-config annotation support
                var pkgconfigs = new Array[String]
                for f in compiler.extern_bodies do
                        pkgconfigs.add_all f.pkgconfigs
                end
-               # Protect pkg-config
+
+               # Only test if pkg-config is used
                if not pkgconfigs.is_empty then
+
+                       # Check availability of pkg-config, silence the proc output
+                       toolcontext.check_pkgconfig_packages pkgconfigs
+
+                       # Double the check in the Makefile in case it's distributed
                        makefile.write """
 # does pkg-config exists?
 ifneq ($(shell which pkg-config >/dev/null; echo $$?), 0)
@@ -477,10 +532,10 @@ endif
                end
 
                # Compile each required extern body into a specific .o
+               var java_files = new Array[ExternFile]
                for f in compiler.extern_bodies do
                        var o = f.makefile_rule_name
-                       var ff = f.filename.basename
-                       makefile.write("{o}: {ff}\n")
+                       makefile.write("{o}: {f.filename}\n")
                        makefile.write("\t{f.makefile_rule_content}\n\n")
                        dep_rules.add(f.makefile_rule_name)
 
@@ -507,9 +562,9 @@ endif
                end
                makefile.write("{outpath}: {dep_rules.join(" ")}\n\t$(CC) $(LDFLAGS) -o {outpath.escape_to_sh} {ofiles.join(" ")} $(LDLIBS) {pkg}\n\n")
                # Clean
-               makefile.write("clean:\n\trm {ofiles.join(" ")} 2>/dev/null\n")
+               makefile.write("clean:\n\trm -f {ofiles.join(" ")} 2>/dev/null\n")
                if outpath != real_outpath then
-                       makefile.write("\trm -- {outpath.escape_to_sh} 2>/dev/null\n")
+                       makefile.write("\trm -f -- {outpath.escape_to_sh} 2>/dev/null\n")
                end
                makefile.close
                self.toolcontext.info("Generated makefile: {makepath}", 2)
@@ -663,7 +718,7 @@ abstract class AbstractCompiler
                stream.write("const char* get_nit_name(register const char* procname, register unsigned int len);\n")
                stream.close
 
-               extern_bodies.add(new ExternCFile("{compile_dir}/c_functions_hash.c", ""))
+               extern_bodies.add(new ExternCFile("c_functions_hash.c", ""))
        end
 
        # Compile C headers
@@ -682,6 +737,7 @@ abstract class AbstractCompiler
                self.header.add_decl("#endif")
                self.header.add_decl("#include <inttypes.h>\n")
                self.header.add_decl("#include \"gc_chooser.h\"")
+               if modelbuilder.toolcontext.opt_trace.value then self.header.add_decl("#include \"traces.h\"")
                self.header.add_decl("#ifdef __APPLE__")
                self.header.add_decl("  #include <TargetConditionals.h>")
                self.header.add_decl("  #include <syslog.h>")
@@ -691,10 +747,6 @@ abstract class AbstractCompiler
                self.header.add_decl("#ifdef _WIN32")
                self.header.add_decl("  #define be32toh(val) _byteswap_ulong(val)")
                self.header.add_decl("#endif")
-               self.header.add_decl("#ifdef __pnacl__")
-               self.header.add_decl("  #define be16toh(val) (((val) >> 8) | ((val) << 8))")
-               self.header.add_decl("  #define be32toh(val) ((be16toh((val) << 16) | (be16toh((val) >> 16))))")
-               self.header.add_decl("#endif")
                self.header.add_decl("#ifdef ANDROID")
                self.header.add_decl("  #ifndef be32toh")
                self.header.add_decl("          #define be32toh(val) betoh32(val)")
@@ -743,9 +795,10 @@ abstract class AbstractCompiler
                self.header.add_decl """
 struct catch_stack_t {
        int cursor;
-       jmp_buf envs[100];
+       int currentSize;
+       jmp_buf *envs;
 };
-extern struct catch_stack_t catchStack;
+extern struct catch_stack_t *getCatchStack();
 """
        end
 
@@ -846,7 +899,44 @@ extern void nitni_global_ref_decr( struct nitni_ref *ref );
                v.add_decl("int glob_argc;")
                v.add_decl("char **glob_argv;")
                v.add_decl("val *glob_sys;")
-               v.add_decl("struct catch_stack_t catchStack;")
+
+               # Store catch stack in thread local storage
+               v.add_decl """
+#if defined(TARGET_OS_IPHONE)
+       // Use pthread_key_create and others for iOS
+       #include <pthread.h>
+
+       static pthread_key_t catch_stack_key;
+       static pthread_once_t catch_stack_key_created = PTHREAD_ONCE_INIT;
+
+       static void create_catch_stack()
+       {
+               pthread_key_create(&catch_stack_key, NULL);
+       }
+
+       struct catch_stack_t *getCatchStack()
+       {
+               pthread_once(&catch_stack_key_created, &create_catch_stack);
+               struct catch_stack_t *data = pthread_getspecific(catch_stack_key);
+               if (data == NULL) {
+                       data = malloc(sizeof(struct catch_stack_t));
+                       data->cursor = -1;
+                       data->currentSize = 0;
+                       data->envs = NULL;
+                       pthread_setspecific(catch_stack_key, data);
+               }
+               return data;
+       }
+#else
+       // Use __thread when available
+       __thread struct catch_stack_t catchStack = {-1, 0, NULL};
+
+       struct catch_stack_t *getCatchStack()
+       {
+               return &catchStack;
+       }
+#endif
+"""
 
                if self.modelbuilder.toolcontext.opt_typing_test_metrics.value then
                        for tag in count_type_test_tags do
@@ -947,7 +1037,6 @@ extern void nitni_global_ref_decr( struct nitni_ref *ref );
                v.add "#endif"
 
                v.add("glob_argc = argc; glob_argv = argv;")
-               v.add("catchStack.cursor = -1;")
                v.add("initialize_gc_option();")
 
                v.add "initialize_nitni_global_refs();"
@@ -1368,6 +1457,11 @@ abstract class AbstractCompilerVisitor
                mtype = self.anchor(mtype)
                var valmtype = value.mcasttype
 
+               # CPrimitive is the best you can do
+               if valmtype.is_c_primitive then
+                       return value
+               end
+
                # Do nothing if useless autocast
                if valmtype.is_subtype(self.compiler.mainmodule, null, mtype) then
                        return value
@@ -1711,7 +1805,7 @@ abstract class AbstractCompilerVisitor
                var nat = new_var(mtype)
                var byte_esc = new Buffer.with_cap(len * 4)
                for i in [0 .. len[ do
-                       byte_esc.append("\\x{ns[i].to_s.substring_from(2)}")
+                       byte_esc.append("\\x{ns[i].to_hex}")
                end
                self.add("{nat} = \"{byte_esc}\";")
                return nat
@@ -1835,8 +1929,11 @@ abstract class AbstractCompilerVisitor
        # This method should be called before the error messages and before a `add_raw_abort`.
        fun add_raw_throw
        do
-               self.add("if(catchStack.cursor >= 0)\{")
-               self.add("longjmp(catchStack.envs[catchStack.cursor], 1);")
+               self.add("\{")
+               self.add("struct catch_stack_t *catchStack = getCatchStack();")
+               self.add("if(catchStack->cursor >= 0)\{")
+               self.add("      longjmp(catchStack->envs[catchStack->cursor], 1);")
+               self.add("\}")
                self.add("\}")
        end
 
@@ -3480,14 +3577,25 @@ redef class ADoExpr
        redef fun stmt(v)
        do
                if self.n_catch != null then
-                       v.add("catchStack.cursor += 1;")
-                       v.add("if(!setjmp(catchStack.envs[catchStack.cursor]))\{")
+                       v.add("\{")
+                       v.add("struct catch_stack_t *catchStack = getCatchStack();")
+                       v.add("if(catchStack->currentSize == 0) \{")
+                       v.add("         catchStack->cursor = -1;")
+                       v.add("         catchStack->currentSize = 100;")
+                       v.add("         catchStack->envs = malloc(sizeof(jmp_buf)*100);")
+                       v.add("\} else if(catchStack->cursor == catchStack->currentSize - 1) \{")
+                       v.add("         catchStack->currentSize *= 2;")
+                       v.add("         catchStack->envs = realloc(catchStack->envs, sizeof(jmp_buf)*catchStack->currentSize);")
+                       v.add("\}")
+                       v.add("catchStack->cursor += 1;")
+                       v.add("if(!setjmp(catchStack->envs[catchStack->cursor]))\{")
                        v.stmt(self.n_block)
-                       v.add("catchStack.cursor -= 1;")
+                       v.add("catchStack->cursor -= 1;")
                        v.add("\}else \{")
-                       v.add("catchStack.cursor -= 1;")
+                       v.add("catchStack->cursor -= 1;")
                        v.stmt(self.n_catch)
                        v.add("\}")
+                       v.add("\}")
                else
                        v.stmt(self.n_block)
                end
@@ -3592,6 +3700,9 @@ redef class AAssertExpr
                var cond = v.expr_bool(self.n_expr)
                v.add("if (unlikely(!{cond})) \{")
                v.stmt(self.n_else)
+
+               explain_assert v
+
                var nid = self.n_id
                if nid != null then
                        v.add_abort("Assert '{nid.text}' failed")
@@ -3600,6 +3711,27 @@ redef class AAssertExpr
                end
                v.add("\}")
        end
+
+       # Explain assert if it fails
+       private fun explain_assert(v: AbstractCompilerVisitor)
+       do
+               var explain_assert_str = explain_assert_str
+               if explain_assert_str == null then return
+
+               var nas = v.compiler.modelbuilder.model.get_mclasses_by_name("NativeArray")
+               if nas == null then return
+
+               nas = v.compiler.modelbuilder.model.get_mclasses_by_name("Array")
+               if nas == null or nas.is_empty then return
+
+               var expr = explain_assert_str.expr(v)
+               if expr == null then return
+
+               var cstr = v.send(v.get_property("to_cstring", expr.mtype), [expr])
+               if cstr == null then return
+
+               v.add "PRINT_ERROR(\"Runtime assert: %s\\n\", {cstr});"
+       end
 end
 
 redef class AOrExpr
@@ -3693,8 +3825,9 @@ end
 
 redef class ACharExpr
        redef fun expr(v) do
-               if is_ascii then return v.byte_instance(value.as(not null).ascii)
-               if is_code_point then return v.int_instance(value.as(not null).code_point)
+               if is_code_point then
+                       return v.int_instance(value.as(not null).code_point)
+               end
                return v.char_instance(self.value.as(not null))
        end
 end
@@ -3952,10 +4085,24 @@ redef class ASendExpr
        redef fun expr(v)
        do
                var recv = v.expr(self.n_expr, null)
+               if is_safe then
+                       v.add "if ({recv}!=NULL) \{"
+               end
                var callsite = self.callsite.as(not null)
                if callsite.is_broken then return null
                var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.raw_arguments)
-               return v.compile_callsite(callsite, args)
+               var res = v.compile_callsite(callsite, args)
+               if is_safe then
+                       if res != null then
+                               var orig_res = res
+                               res = v.new_var(self.mtype.as(not null))
+                               v.add("{res} = {orig_res};")
+                               v.add("\} else \{")
+                               v.add("{res} = NULL;")
+                       end
+                       v.add("\}")
+               end
+               return res
        end
 end
 
@@ -4123,6 +4270,13 @@ redef class AVarargExpr
        end
 end
 
+redef class ASafeExpr
+       redef fun expr(v)
+       do
+               return v.expr(self.n_expr, null)
+       end
+end
+
 redef class ANamedargExpr
        redef fun expr(v)
        do
@@ -4194,6 +4348,10 @@ var model = new Model
 var modelbuilder = new ModelBuilder(model, toolcontext)
 
 var arguments = toolcontext.option_context.rest
+if toolcontext.opt_run.value then
+       # When --run, only the first is the program, the rest is the run arguments
+       arguments = [toolcontext.option_context.rest.shift]
+end
 if arguments.length > 1 and toolcontext.opt_output.value != null then
        print "Option Error: --output needs a single source file. Do you prefer --dir?"
        exit 1