X-Git-Url: http://nitlanguage.org diff --git a/src/compiler/abstract_compiler.nit b/src/compiler/abstract_compiler.nit index d608967..c74933c 100644 --- a/src/compiler/abstract_compiler.nit +++ b/src/compiler/abstract_compiler.nit @@ -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) @@ -349,14 +379,34 @@ class MakefileToolchain end var debug = toolcontext.opt_debug.value - makefile.write("CC = ccache cc\nCXX = ccache c++\nCFLAGS = -g{ if not debug then " -O2 " else " "}-Wno-unused-value -Wno-switch -Wno-attributes -Wno-trigraphs\nCINCL =\nLDFLAGS ?= \nLDLIBS ?= -lm {linker_options.join(" ")}\n\n") + makefile.write """ +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 - makefile.write "NO_STACKTRACE=True" + makefile.write "NO_STACKTRACE ?= True" else - makefile.write "NO_STACKTRACE= # Set to `True` to enable" + makefile.write "NO_STACKTRACE ?= # Set to `True` to enable" end end @@ -403,6 +453,25 @@ ifeq ($(uname_S),Darwin) LDLIBS := $(filter-out -lrt,$(LDLIBS)) endif +# Special configuration for Windows under mingw64 +ifneq ($(findstring MINGW64,$(uname_S)),) + # Use the pcreposix regex library + LDLIBS += -lpcreposix + + # Remove POSIX flag -lrt + LDLIBS := $(filter-out -lrt,$(LDLIBS)) + + # Silence warnings when storing Int, Char and Bool as pointer address + 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") @@ -433,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) @@ -458,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) @@ -488,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) @@ -510,8 +584,10 @@ endif self.toolcontext.info(command, 2) var res - if self.toolcontext.verbose_level >= 3 or is_windows then + if self.toolcontext.verbose_level >= 3 then res = sys.system("{command} 2>&1") + else if is_windows then + res = sys.system("{command} 2>&1 >nul") else res = sys.system("{command} 2>&1 >/dev/null") end @@ -642,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 @@ -661,6 +737,7 @@ abstract class AbstractCompiler self.header.add_decl("#endif") self.header.add_decl("#include \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 ") self.header.add_decl(" #include ") @@ -670,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)") @@ -722,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 @@ -825,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 + + 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 @@ -926,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();" @@ -1347,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 @@ -1429,7 +1544,7 @@ abstract class AbstractCompilerVisitor # Checks # Can value be null? (according to current knowledge) - fun maybenull(value: RuntimeVariable): Bool + fun maybe_null(value: RuntimeVariable): Bool do return value.mcasttype isa MNullableType or value.mcasttype isa MNullType end @@ -1439,7 +1554,7 @@ abstract class AbstractCompilerVisitor do if self.compiler.modelbuilder.toolcontext.opt_no_check_null.value then return - if maybenull(recv) then + if maybe_null(recv) then self.add("if (unlikely({recv} == NULL)) \{") self.add_abort("Receiver is null") self.add("\}") @@ -1690,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 @@ -1804,17 +1919,33 @@ abstract class AbstractCompilerVisitor # used by aborts, asserts, casts, etc. fun add_abort(message: String) do - self.add("if(catchStack.cursor >= 0)\{") - self.add("longjmp(catchStack.envs[catchStack.cursor], 1);") - self.add("\}") + add_raw_throw self.add("PRINT_ERROR(\"Runtime error: %s\", \"{message.escape_to_c}\");") add_raw_abort end + # Generate a long jump if there is a catch block. + # + # This method should be called before the error messages and before a `add_raw_abort`. + fun add_raw_throw + do + 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 + + # Generate abort without a message. + # + # Used when one need a more complex message. + # Do not forget to call `add_raw_abort` before the display of a custom user message. fun add_raw_abort do - if self.current_node != null and self.current_node.location.file != null and - self.current_node.location.file.mmodule != null then + var current_node = self.current_node + if current_node != null and current_node.location.file != null and + current_node.location.file.mmodule != null then var f = "FILE_{self.current_node.location.file.mmodule.c_name}" self.require_declaration(f) self.add("PRINT_ERROR(\" (%s:%d)\\n\", {f}, {current_node.location.line_start});") @@ -1829,6 +1960,7 @@ abstract class AbstractCompilerVisitor do var res = self.type_test(value, mtype, tag) self.add("if (unlikely(!{res})) \{") + self.add_raw_throw var cn = self.class_name_string(value) self.add("PRINT_ERROR(\"Runtime error: Cast failed. Expected `%s`, got `%s`\", \"{mtype.to_s.escape_to_c}\", {cn});") self.add_raw_abort @@ -2158,6 +2290,7 @@ redef class MMethodDef var node = modelbuilder.mpropdef2node(self) if is_abstract then + v.add_raw_throw var cn = v.class_name_string(arguments.first) v.current_node = node v.add("PRINT_ERROR(\"Runtime error: Abstract method `%s` called on `%s`\", \"{mproperty.name.escape_to_c}\", {cn});") @@ -2269,6 +2402,7 @@ redef class AMethPropdef end # We have a problem + v.add_raw_throw var cn = v.class_name_string(arguments.first) v.add("PRINT_ERROR(\"Runtime error: uncompiled method `%s` called on `%s`. NOT YET IMPLEMENTED\", \"{mpropdef.mproperty.name.escape_to_c}\", {cn});") v.add_raw_abort @@ -3190,7 +3324,7 @@ redef class AAttrPropdef assert arguments.length == 2 var recv = arguments.first var arg = arguments[1] - if is_optional and v.maybenull(arg) then + if is_optional and v.maybe_null(arg) then var value = v.new_var(self.mpropdef.static_mtype.as(not null)) v.add("if ({arg} == NULL) \{") v.assign(value, evaluate_expr(v, recv)) @@ -3443,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 @@ -3555,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") @@ -3563,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 @@ -3624,7 +3793,7 @@ redef class AOrElseExpr var res = v.new_var(self.mtype.as(not null)) var i1 = v.expr(self.n_expr, null) - if not v.maybenull(i1) then return i1 + if not v.maybe_null(i1) then return i1 v.add("if ({i1}!=NULL) \{") v.assign(res, i1) @@ -3656,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 @@ -3877,7 +4047,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 - if not v.maybenull(i) then return i + if not v.maybe_null(i) then return i v.add("if (unlikely({i} == NULL)) \{") v.add_abort("Cast failed") @@ -3915,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 @@ -4086,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 @@ -4157,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