nitc & niti: move the duplicated pkg-config checks to the pkgconfig module
[nit.git] / src / compiler / abstract_compiler.nit
index cddc8d3..1a1bb07 100644 (file)
@@ -24,6 +24,7 @@ import c_tools
 private import annotation
 import mixin
 import counter
+import pkgconfig
 
 # Add compiling options
 redef class ToolContext
@@ -31,6 +32,8 @@ 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
@@ -75,7 +78,7 @@ redef class ToolContext
        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_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)
@@ -186,6 +189,8 @@ class MakefileToolchain
                var time1 = get_time
                self.toolcontext.info("*** END WRITING C: {time1-time0} ***", 2)
 
+               if not toolcontext.check_errors then return
+
                # Execute the Makefile
 
                if self.toolcontext.opt_no_cc.value then return
@@ -199,6 +204,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
@@ -349,14 +362,25 @@ 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"""
 
                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 +427,18 @@ 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
+
 """
 
                makefile.write("all: {outpath}\n")
@@ -433,14 +469,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,6 +499,7 @@ 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
@@ -512,6 +554,8 @@ endif
                var res
                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
@@ -662,9 +706,14 @@ abstract class AbstractCompiler
                self.header.add_decl("#include <inttypes.h>\n")
                self.header.add_decl("#include \"gc_chooser.h\"")
                self.header.add_decl("#ifdef __APPLE__")
+               self.header.add_decl("  #include <TargetConditionals.h>")
+               self.header.add_decl("  #include <syslog.h>")
                self.header.add_decl("  #include <libkern/OSByteOrder.h>")
                self.header.add_decl("  #define be32toh(x) OSSwapBigToHostInt32(x)")
                self.header.add_decl("#endif")
+               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))))")
@@ -675,6 +724,8 @@ abstract class AbstractCompiler
                self.header.add_decl("  #endif")
                self.header.add_decl("  #include <android/log.h>")
                self.header.add_decl("  #define PRINT_ERROR(...) (void)__android_log_print(ANDROID_LOG_WARN, \"Nit\", __VA_ARGS__)")
+               self.header.add_decl("#elif TARGET_OS_IPHONE")
+               self.header.add_decl("  #define PRINT_ERROR(...) syslog(LOG_ERR, __VA_ARGS__)")
                self.header.add_decl("#else")
                self.header.add_decl("  #define PRINT_ERROR(...) fprintf(stderr, __VA_ARGS__)")
                self.header.add_decl("#endif")
@@ -878,11 +929,17 @@ extern void nitni_global_ref_decr( struct nitni_ref *ref );
                v.add_decl("\}")
 
                v.add_decl("void sig_handler(int signo)\{")
+               v.add_decl "#ifdef _WIN32"
+               v.add_decl "PRINT_ERROR(\"Caught signal : %s\\n\", signo);"
+               v.add_decl "#else"
                v.add_decl("PRINT_ERROR(\"Caught signal : %s\\n\", strsignal(signo));")
+               v.add_decl "#endif"
                v.add_decl("show_backtrace();")
                # rethrows
                v.add_decl("signal(signo, SIG_DFL);")
+               v.add_decl "#ifndef _WIN32"
                v.add_decl("kill(getpid(), signo);")
+               v.add_decl "#endif"
                v.add_decl("\}")
 
                v.add_decl("void fatal_exit(int status) \{")
@@ -908,7 +965,9 @@ extern void nitni_global_ref_decr( struct nitni_ref *ref );
                v.add("signal(SIGTERM, sig_handler);")
                v.add("signal(SIGSEGV, sig_handler);")
                v.add "#endif"
+               v.add "#ifndef _WIN32"
                v.add("signal(SIGPIPE, SIG_IGN);")
+               v.add "#endif"
 
                v.add("glob_argc = argc; glob_argv = argv;")
                v.add("catchStack.cursor = -1;")
@@ -1134,25 +1193,41 @@ extern void nitni_global_ref_decr( struct nitni_ref *ref ) {
        fun finalize_ffi_for_module(mmodule: MModule) do mmodule.finalize_ffi(self)
 end
 
-# A file unit (may be more than one file if
-# A file unit aim to be autonomous and is made or one or more `CodeWriter`s
+# C code file generated from the `writers` and the `required_declarations`
+#
+# A file unit aims to be autonomous and is made or one or more `CodeWriter`s.
 class CodeFile
+       # Basename of the file, will be appended by `.h` and `.c`
        var name: String
+
+       # `CodeWriter` used in sequence to fill the top of the body, then the bottom
        var writers = new Array[CodeWriter]
+
+       # Required declarations keys
+       #
+       # See: `provide_declaration`
        var required_declarations = new HashSet[String]
 end
 
-# Where to store generated lines
+# Store generated lines
+#
+# Instances are added to `file.writers` at construction.
 class CodeWriter
+       # Parent `CodeFile`
        var file: CodeFile
+
+       # Main lines of code (written at the bottom of the body)
        var lines = new Array[String]
+
+       # Lines of code for declarations (written at the top of the body)
        var decl_lines = new Array[String]
 
-       # Add a line in the main part of the generated C
+       # Add a line in the main lines of code (to `lines`)
        fun add(s: String) do self.lines.add(s)
 
-       # Add a line in the
-       # (used for local or global declaration)
+       # Add a declaration line (to `decl_lines`)
+       #
+       # Used for local and global declaration.
        fun add_decl(s: String) do self.decl_lines.add(s)
 
        init
@@ -1398,7 +1473,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
@@ -1408,7 +1483,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("\}")
@@ -1460,7 +1535,7 @@ abstract class AbstractCompilerVisitor
        end
 
        # Return a "const char*" variable associated to the classname of the dynamic type of an object
-       # NOTE: we do not return a `RuntimeVariable` "NativeString" as the class may not exist in the module/program
+       # NOTE: we do not return a `RuntimeVariable` "CString" as the class may not exist in the module/program
        fun class_name_string(value: RuntimeVariable): String is abstract
 
        # Variables handling
@@ -1579,7 +1654,7 @@ abstract class AbstractCompilerVisitor
        fun int8_instance(value: Int8): RuntimeVariable
        do
                var t = mmodule.int8_type
-               var res = new RuntimeVariable("((int8_t){value.to_s})", t, t)
+               var res = new RuntimeVariable("INT8_C({value.to_s})", t, t)
                return res
        end
 
@@ -1587,7 +1662,7 @@ abstract class AbstractCompilerVisitor
        fun int16_instance(value: Int16): RuntimeVariable
        do
                var t = mmodule.int16_type
-               var res = new RuntimeVariable("((int16_t){value.to_s})", t, t)
+               var res = new RuntimeVariable("INT16_C({value.to_s})", t, t)
                return res
        end
 
@@ -1595,7 +1670,7 @@ abstract class AbstractCompilerVisitor
        fun uint16_instance(value: UInt16): RuntimeVariable
        do
                var t = mmodule.uint16_type
-               var res = new RuntimeVariable("((uint16_t){value.to_s})", t, t)
+               var res = new RuntimeVariable("UINT16_C({value.to_s})", t, t)
                return res
        end
 
@@ -1603,7 +1678,7 @@ abstract class AbstractCompilerVisitor
        fun int32_instance(value: Int32): RuntimeVariable
        do
                var t = mmodule.int32_type
-               var res = new RuntimeVariable("((int32_t){value.to_s})", t, t)
+               var res = new RuntimeVariable("INT32_C({value.to_s})", t, t)
                return res
        end
 
@@ -1611,7 +1686,7 @@ abstract class AbstractCompilerVisitor
        fun uint32_instance(value: UInt32): RuntimeVariable
        do
                var t = mmodule.uint32_type
-               var res = new RuntimeVariable("((uint32_t){value.to_s})", t, t)
+               var res = new RuntimeVariable("UINT32_C({value.to_s})", t, t)
                return res
        end
 
@@ -1653,9 +1728,9 @@ abstract class AbstractCompilerVisitor
                return res
        end
 
-       # Generates a NativeString instance fully escaped in C-style \xHH fashion
-       fun native_string_instance(ns: NativeString, len: Int): RuntimeVariable do
-               var mtype = mmodule.native_string_type
+       # Generates a CString instance fully escaped in C-style \xHH fashion
+       fun c_string_instance(ns: CString, len: Int): RuntimeVariable do
+               var mtype = mmodule.c_string_type
                var nat = new_var(mtype)
                var byte_esc = new Buffer.with_cap(len * 4)
                for i in [0 .. len[ do
@@ -1675,12 +1750,12 @@ abstract class AbstractCompilerVisitor
                self.add("if (likely({name}!=NULL)) \{")
                self.add("{res} = {name};")
                self.add("\} else \{")
-               var native_mtype = mmodule.native_string_type
+               var native_mtype = mmodule.c_string_type
                var nat = self.new_var(native_mtype)
                self.add("{nat} = \"{string.escape_to_c}\";")
                var byte_length = self.int_instance(string.byte_length)
                var unilen = self.int_instance(string.length)
-               self.add("{res} = {self.send(self.get_property("to_s_full", native_mtype), [nat, byte_length, unilen]).as(not null)};")
+               self.add("{res} = {self.send(self.get_property("to_s_unsafe", native_mtype), [nat, byte_length, unilen, value_instance(false), value_instance(false)]).as(not null)};")
                self.add("{name} = {res};")
                self.add("\}")
                return res
@@ -1773,17 +1848,30 @@ abstract class AbstractCompilerVisitor
        # used by aborts, asserts, casts, etc.
        fun add_abort(message: String)
        do
+               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("if(catchStack.cursor >= 0)\{")
                self.add("longjmp(catchStack.envs[catchStack.cursor], 1);")
                self.add("\}")
-               self.add("PRINT_ERROR(\"Runtime error: %s\", \"{message.escape_to_c}\");")
-               add_raw_abort
        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});")
@@ -1798,6 +1886,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
@@ -2042,7 +2131,7 @@ redef class MClassType
                        return "int32_t"
                else if mclass.name == "UInt32" then
                        return "uint32_t"
-               else if mclass.name == "NativeString" then
+               else if mclass.name == "CString" then
                        return "char*"
                else if mclass.name == "NativeArray" then
                        return "val*"
@@ -2084,7 +2173,7 @@ redef class MClassType
                        return "i32"
                else if mclass.name == "UInt32" then
                        return "u32"
-               else if mclass.name == "NativeString" then
+               else if mclass.name == "CString" then
                        return "str"
                else if mclass.name == "NativeArray" then
                        #return "{self.arguments.first.ctype}*"
@@ -2127,6 +2216,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});")
@@ -2238,6 +2328,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
@@ -2553,7 +2644,7 @@ redef class AMethPropdef
                                v.ret(v.new_expr("(uint32_t){arguments[0]}", ret.as(not null)))
                                return true
                        end
-               else if cname == "NativeString" then
+               else if cname == "CString" then
                        if pname == "[]" then
                                v.ret(v.new_expr("(unsigned char)((int){arguments[0]}[{arguments[1]}])", ret.as(not null)))
                                return true
@@ -2577,14 +2668,14 @@ redef class AMethPropdef
                                v.ret(v.new_expr("!{res}", ret.as(not null)))
                                return true
                        else if pname == "new" then
-                               var alloc = v.nit_alloc(arguments[1].to_s, "NativeString")
+                               var alloc = v.nit_alloc(arguments[1].to_s, "CString")
                                v.ret(v.new_expr("(char*){alloc}", ret.as(not null)))
                                return true
                        else if pname == "fetch_4_chars" then
-                               v.ret(v.new_expr("(long)*((uint32_t*)({arguments[0]} + {arguments[1]}))", ret.as(not null)))
+                               v.ret(v.new_expr("*((uint32_t*)({arguments[0]} + {arguments[1]}))", ret.as(not null)))
                                return true
                        else if pname == "fetch_4_hchars" then
-                               v.ret(v.new_expr("(long)be32toh(*((uint32_t*)({arguments[0]} + {arguments[1]})))", ret.as(not null)))
+                               v.ret(v.new_expr("(uint32_t)be32toh(*((uint32_t*)({arguments[0]} + {arguments[1]})))", ret.as(not null)))
                                return true
                        end
                else if cname == "NativeArray" then
@@ -3026,7 +3117,7 @@ redef class AMethPropdef
                        end
                end
                if pname == "exit" then
-                       v.add("exit({arguments[1]});")
+                       v.add("exit((int){arguments[1]});")
                        return true
                else if pname == "sys" then
                        v.ret(v.new_expr("glob_sys", ret.as(not null)))
@@ -3159,7 +3250,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))
@@ -3593,7 +3684,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)
@@ -3695,7 +3786,7 @@ redef class AStringExpr
                var s = v.string_instance(value)
                if is_string then return s
                if is_bytestring then
-                       var ns = v.native_string_instance(bytes.items, bytes.length)
+                       var ns = v.c_string_instance(bytes.items, bytes.length)
                        var ln = v.int_instance(bytes.length)
                        var cs = to_bytes_with_copy
                        assert cs != null
@@ -3769,7 +3860,7 @@ redef class ASuperstringExpr
                        v.native_array_set(a, i, e)
                end
 
-               # Fast join the native string to get the result
+               # Fast join the C string to get the result
                var res = v.send(v.get_property("native_to_s", a.mtype), [a])
                assert res != null
 
@@ -3846,7 +3937,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")
@@ -4126,6 +4217,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