Merge: Portable stack-traces
authorJean Privat <jean@pryen.org>
Thu, 4 Jun 2015 10:34:57 +0000 (06:34 -0400)
committerJean Privat <jean@pryen.org>
Thu, 4 Jun 2015 10:34:57 +0000 (06:34 -0400)
Most detection of the availability of libunwind and the flags to use is moved at the C compilation time (in the Makefile) instead at the Nit compilation time.

Moreover, if libunwind is not available, then stacktraces are just disabled (instead of aborting).

Another advantage is that the generated C is more portable and can be compiled in a different system.
Thus, this will help cross-compilation and should even enable the bootstrap on libunwind-less system like raspberrypi once c_src is regenerated (cf #1149).

The option `--stacktrace` is also replaced with a simpler `--no-stacktrace` for people that want to disable it completely at C-compile time.

Close #1357 and an item in #864.

Pull-Request: #1419
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>

1  2 
share/man/nitc.md
src/compiler/abstract_compiler.nit

diff --combined share/man/nitc.md
@@@ -20,7 -20,7 +20,7 @@@ By default, the generated executables a
  
  Internally, nitc rely on the presence of a C compiler. Usually gcc (but nitc was successfully tested with clang).
  A compilation directory is therefore created and (re-)used.
 -By default, the compilation directory is named `.nit_compile`.
 +By default, the compilation directory is named `nit_compile` and is removed after the compilation.
  (see `--compile-dir` for details.)
  
  Currently, because Nit is still in heavy development, the compilation directory is not cleaned after the compilation.
@@@ -168,9 -168,7 +168,9 @@@ See the documentation of these specifi
  `--compile-dir`
  :   Directory used to generate temporary files.
  
 -    By default, it is named `.nit_compile`.
 +    By default, it is named `nit_compile` and created in the current directory and destroyed after the compilation.
 +
 +    If the option `--compile_dir` or `--no-cc` is used, then the directory is not destroyed and let as is.
  
  `--no-cc`
  :   Do not invoke the C compiler.
      Only the C files required for the program are generated.
      The final binary will be generated in the same directory.
  
 +    Note that, to be useful, the compilation directory is not destroyed when `--no-cc` is used.
 +
  `-m`
  :   Additional module to mix-in.
  
@@@ -404,8 -400,18 +404,18 @@@ They are useless for a normal user
  `--no-main`
  :   Do not generate main entry point.
  
- `--stacktrace`
- :   Control the generation of stack traces.
+ `--no-stacktrace`
+ :   The compiled program will not display stack traces on runtime errors.
+     Because stack traces rely on libunwind, this option might be useful in order to generate more portable binaries
+     since libunwind might be non available on the runtime system (or available with an ABI incompatible version).
+     The generated C is API-portable and can be reused, distributed and compiled on any supported system.
+     If the option `--no-stacktrace` is not used but the development files of the library `libunwind` are not available, then a warning will be displayed
+     and stack trace will be disabled.
+     Note that the `--no-stacktrace` option (or this absence) can be toggled manually in the generated Makefile (search `NO_STACKTRACE` in the Makefile).
+     Moreover, the environment variable `NIT_NO_STACK` (see bellow) can also be used at runtime to disable stack traces.
  
  `--max-c-lines`
  :   Maximum number of lines in generated C files. Use 0 for unlimited.
@@@ -481,6 -487,20 +491,20 @@@ This option is used to test the robustn
      * large: disable the GC and just allocate a large memory area to use for all instantiation.
      * help: show the list of available options.
  
+ `NIT_NO_STACK`
+ :   Runtime control of stack traces.
+     By default, stack traces are printed when a runtime errors occurs during the execution of a compiled program.
+     When setting this environment variable to a non empty value, such stack traces are disabled.
+     The environment variable is used when programs are executed, not when they are compiled.
+     Thus, you do not need to recompile programs in order to disable generated stack traces.
+     Note that stack traces require that, during the compilation, development files of the library `libunwind` are available.
+     If they are not available, then programs are compiled without any stack trace support.
+     To completely disable stack traces, see the option `--no-stacktrace`.
  # SEE ALSO
  
  The Nit language documentation and the source code of its tools and libraries may be downloaded from <http://nitlanguage.org>
@@@ -63,8 -63,8 +63,8 @@@ redef class ToolContex
        var opt_invocation_metrics = new OptionBool("Enable static and dynamic count of all method invocations", "--invocation-metrics")
        # --isset-checks-metrics
        var opt_isset_checks_metrics = new OptionBool("Enable static and dynamic count of isset checks before attributes access", "--isset-checks-metrics")
-       # --stacktrace
-       var opt_stacktrace = new OptionString("Control the generation of stack traces", "--stacktrace")
+       # --no-stacktrace
+       var opt_no_stacktrace = new OptionBool("Disable the generation of stack traces", "--no-stacktrace")
        # --no-gcc-directives
        var opt_no_gcc_directive = new OptionArray("Disable a advanced gcc directives for optimization", "--no-gcc-directive")
        # --release
@@@ -76,7 -76,7 +76,7 @@@
                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_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_stacktrace)
+               self.option_context.add_option(self.opt_no_stacktrace)
                self.option_context.add_option(self.opt_no_gcc_directive)
                self.option_context.add_option(self.opt_release)
                self.option_context.add_option(self.opt_max_c_lines, self.opt_group_c_files)
        do
                super
  
-               var st = opt_stacktrace.value
-               if st == "none" or st == "libunwind" or st == "nitstack" then
-                       # Fine, do nothing
-               else if st == "auto" or st == null then
-                       # Default is nitstack
-                       opt_stacktrace.value = "nitstack"
-               else
-                       print "Option Error: unknown value `{st}` for --stacktrace. Use `none`, `libunwind`, `nitstack` or `auto`."
-                       exit(1)
-               end
                if opt_output.value != null and opt_dir.value != null then
                        print "Option Error: cannot use both --dir and --output"
                        exit(1)
  end
  
  redef class ModelBuilder
 -      # The compilation directory
 -      var compile_dir: String
 -
        # Simple indirection to `Toolchain::write_and_make`
        protected fun write_and_make(compiler: AbstractCompiler)
        do
                var platform = compiler.target_platform
                var toolchain = platform.toolchain(toolcontext, compiler)
 -              compile_dir = toolchain.compile_dir
 +              compiler.toolchain = toolchain
                toolchain.write_and_make
        end
  end
@@@ -142,21 -134,14 +131,21 @@@ class Toolchai
        # Compiler of the target program
        var compiler: AbstractCompiler
  
 -      # Directory where to generate all C files
 -      fun compile_dir: String
 +      # Directory where to generate all files
 +      #
 +      # The option `--compile_dir` change this directory.
 +      fun root_compile_dir: String
        do
                var compile_dir = toolcontext.opt_compile_dir.value
 -              if compile_dir == null then compile_dir = ".nit_compile"
 +              if compile_dir == null then compile_dir = "nit_compile"
                return compile_dir
        end
  
 +      # Directory where to generate all C files
 +      #
 +      # By default it is `root_compile_dir` but some platform may require that it is a subdirectory.
 +      fun compile_dir: String do return root_compile_dir
 +
        # Write all C files and compile them
        fun write_and_make is abstract
  end
@@@ -169,16 -154,12 +158,16 @@@ class MakefileToolchai
        do
                var compile_dir = compile_dir
  
 +              # Remove the compilation directory unless explicitly set
 +              var auto_remove = toolcontext.opt_compile_dir.value == null
 +
                # Generate the .h and .c files
                # A single C file regroups many compiled rumtime functions
                # Note that we do not try to be clever an a small change in a Nit source file may change the content of all the generated .c files
                var time0 = get_time
                self.toolcontext.info("*** WRITING C ***", 1)
  
 +              root_compile_dir.mkdir
                compile_dir.mkdir
  
                var cfiles = new Array[String]
  
                compile_c_code(compile_dir)
  
 +              if auto_remove then
 +                      sys.system("rm -r -- '{root_compile_dir.escape_to_sh}/'")
 +              end
 +
                time1 = get_time
                self.toolcontext.info("*** END COMPILING C: {time1-time0} ***", 2)
        end
        fun write_files(compile_dir: String, cfiles: Array[String])
        do
                var platform = compiler.target_platform
-               if self.toolcontext.opt_stacktrace.value == "nitstack" and platform.supports_libunwind then compiler.build_c_to_nit_bindings
+               if platform.supports_libunwind then compiler.build_c_to_nit_bindings
                var cc_opt_with_libgc = "-DWITH_LIBGC"
                if not platform.supports_libgc then cc_opt_with_libgc = ""
  
                var outpath = real_outpath.escape_to_mk
                if outpath != real_outpath then
                        # If the name is crazy and need escaping, we will do an indirection
 -                      # 1. generate the binary in the .nit_compile dir under an escaped name
 +                      # 1. generate the binary in the nit_compile dir under an escaped name
                        # 2. copy the binary at the right place in the `all` goal.
                        outpath = mainmodule.c_name
                end
  
                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.supports_libunwind then makefile.write("NEED_LIBUNWIND := YesPlease\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"
+                       else
+                               makefile.write "NO_STACKTRACE= # Set to `True` to enable"
+                       end
+               end
  
                # Dynamic adaptations
                # While `platform` enable complex toolchains, they are statically applied
                # For a dynamic adaptsation of the compilation, the generated Makefile should check and adapt things itself
+               makefile.write "\n\n"
  
                # Check and adapt the targeted system
                makefile.write("uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')\n")
-               makefile.write("ifeq ($(uname_S),Darwin)\n")
-               # remove -lunwind since it is already included on macosx
-               makefile.write("\tNEED_LIBUNWIND :=\n")
-               makefile.write("endif\n\n")
  
                # Check and adapt for the compiler used
                # clang need an additionnal `-Qunused-arguments`
                makefile.write("clang_check := $(shell sh -c '$(CC) -v 2>&1 | grep -q clang; echo $$?')\nifeq ($(clang_check), 0)\n\tCFLAGS += -Qunused-arguments\nendif\n")
  
-               makefile.write("ifdef NEED_LIBUNWIND\n\tLDLIBS += -lunwind\nendif\n")
+               if platform.supports_libunwind then
+                       makefile.write """
+ ifneq ($(NO_STACKTRACE), True)
+   # Check and include lib-unwind in a portable way
+   ifneq ($(uname_S),Darwin)
+     # already included on macosx, but need to get the correct flags in other supported platforms.
+     ifeq ($(shell pkg-config --exists 'libunwind'; echo $$?), 0)
+       LDLIBS += `pkg-config --libs libunwind`
+       CFLAGS += `pkg-config --cflags libunwind`
+     else
+       $(warning "[_] stack-traces disabled. Please install libunwind-dev.")
+       CFLAGS += -D NO_STACKTRACE
+     endif
+   endif
+ else
+   # Stacktraces disabled
+   CFLAGS += -D NO_STACKTRACE
+ endif
+ """
+               else
+                       makefile.write("CFLAGS += -D NO_STACKTRACE\n\n")
+               end
  
                makefile.write("all: {outpath}\n")
                if outpath != real_outpath then
@@@ -508,11 -510,6 +522,11 @@@ abstract class AbstractCompile
        # The modelbuilder used to know the model and the AST
        var modelbuilder: ModelBuilder is protected writable
  
 +      # The associated toolchain
 +      #
 +      # Set by `modelbuilder.write_and_make` and permit sub-routines to access the current toolchain if required.
 +      var toolchain: Toolchain is noinit
 +
        # Is hardening asked? (see --hardening)
        fun hardening: Bool do return self.modelbuilder.toolcontext.opt_hardening.value
  
        # Binds the generated C function names to Nit function names
        fun build_c_to_nit_bindings
        do
 -              var compile_dir = modelbuilder.compile_dir
 +              var compile_dir = toolchain.compile_dir
  
                var stream = new FileWriter.open("{compile_dir}/c_functions_hash.c")
                stream.write("#include <string.h>\n")
                self.header.add_decl("#include <string.h>")
                self.header.add_decl("#include <sys/types.h>\n")
                self.header.add_decl("#include <unistd.h>\n")
 +              self.header.add_decl("#include <stdint.h>\n")
                self.header.add_decl("#include \"gc_chooser.h\"")
                self.header.add_decl("#ifdef ANDROID")
                self.header.add_decl("  #include <android/log.h>")
@@@ -731,19 -727,16 +745,16 @@@ extern void nitni_global_ref_decr( stru
        do
                var v = self.new_visitor
                v.add_decl("#include <signal.h>")
-               var ost = modelbuilder.toolcontext.opt_stacktrace.value
                var platform = target_platform
  
-               if not platform.supports_libunwind then ost = "none"
                var no_main = platform.no_main or modelbuilder.toolcontext.opt_no_main.value
  
-               if ost == "nitstack" or ost == "libunwind" then
+               if platform.supports_libunwind then
+                       v.add_decl("#ifndef NO_STACKTRACE")
                        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
+                       v.add_decl("#include \"c_functions_hash.h\"")
+                       v.add_decl("#endif")
                end
                v.add_decl("int glob_argc;")
                v.add_decl("char **glob_argv;")
                end
  
                v.add_decl("static void show_backtrace(void) \{")
-               if ost == "nitstack" or ost == "libunwind" then
+               if platform.supports_libunwind then
+                       v.add_decl("#ifndef NO_STACKTRACE")
                        v.add_decl("char* opt = getenv(\"NIT_NO_STACK\");")
                        v.add_decl("unw_cursor_t cursor;")
                        v.add_decl("if(opt==NULL)\{")
                        v.add_decl("PRINT_ERROR(\"-------------------------------------------------\\n\");")
                        v.add_decl("while (unw_step(&cursor) > 0) \{")
                        v.add_decl("    unw_get_proc_name(&cursor, procname, 100, &ip);")
-                       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("            PRINT_ERROR(\"` %s\\n\", recv);")
                        v.add_decl("    \}else\{")
                        v.add_decl("            PRINT_ERROR(\"` %s\\n\", procname);")
                        v.add_decl("    \}")
-                       else
-                       v.add_decl("    PRINT_ERROR(\"` %s \\n\",procname);")
-                       end
                        v.add_decl("\}")
                        v.add_decl("PRINT_ERROR(\"-------------------------------------------------\\n\");")
                        v.add_decl("free(procname);")
                        v.add_decl("\}")
+                       v.add_decl("#endif /* NO_STACKTRACE */")
                end
                v.add_decl("\}")
  
@@@ -1473,14 -1464,6 +1482,14 @@@ abstract class AbstractCompilerVisito
                return res
        end
  
 +      # Generate a byte value
 +      fun byte_instance(value: Byte): RuntimeVariable
 +      do
 +              var t = mmodule.byte_type
 +              var res = new RuntimeVariable("((unsigned char){value.to_s})", t, t)
 +              return res
 +      end
 +
        # Generate a char value
        fun char_instance(value: Char): RuntimeVariable
        do
@@@ -1861,13 -1844,11 +1870,13 @@@ redef class MClassTyp
                else if mclass.name == "Bool" then
                        return "short int"
                else if mclass.name == "Char" then
 -                      return "char"
 +                      return "uint32_t"
                else if mclass.name == "Float" then
                        return "double"
 +              else if mclass.name == "Byte" then
 +                      return "unsigned char"
                else if mclass.name == "NativeString" then
 -                      return "char*"
 +                      return "unsigned char*"
                else if mclass.name == "NativeArray" then
                        return "val*"
                else
                        return "c"
                else if mclass.name == "Float" then
                        return "d"
 +              else if mclass.name == "Byte" then
 +                      return "b"
                else if mclass.name == "NativeString" then
                        return "str"
                else if mclass.name == "NativeArray" then
@@@ -2128,16 -2107,13 +2137,16 @@@ redef class AMethPropde
                        else if pname == "to_f" then
                                v.ret(v.new_expr("(double){arguments[0]}", ret.as(not null)))
                                return true
 +                      else if pname == "to_b" then
 +                              v.ret(v.new_expr("(unsigned char){arguments[0]}", ret.as(not null)))
 +                              return true
                        else if pname == "ascii" then
 -                              v.ret(v.new_expr("{arguments[0]}", ret.as(not null)))
 +                              v.ret(v.new_expr("(uint32_t){arguments[0]}", ret.as(not null)))
                                return true
                        end
                else if cname == "Char" then
                        if pname == "output" then
 -                              v.add("printf(\"%c\", {arguments.first});")
 +                              v.add("printf(\"%c\", ((unsigned char){arguments.first}));")
                                return true
                        else if pname == "object_id" then
                                v.ret(v.new_expr("(long){arguments.first}", ret.as(not null)))
                                v.ret(v.new_expr("{arguments[0]}-'0'", ret.as(not null)))
                                return true
                        else if pname == "ascii" then
 -                              v.ret(v.new_expr("(unsigned char){arguments[0]}", ret.as(not null)))
 +                              v.ret(v.new_expr("(long){arguments[0]}", ret.as(not null)))
 +                              return true
 +                      end
 +              else if cname == "Byte" then
 +                      if pname == "output" then
 +                              v.add("printf(\"%x\\n\", {arguments.first});")
 +                              return true
 +                      else if pname == "object_id" then
 +                              v.ret(v.new_expr("(long){arguments.first}", ret.as(not null)))
 +                              return true
 +                      else if pname == "+" then
 +                              v.ret(v.new_expr("{arguments[0]} + {arguments[1]}", ret.as(not null)))
 +                              return true
 +                      else if pname == "-" then
 +                              v.ret(v.new_expr("{arguments[0]} - {arguments[1]}", ret.as(not null)))
 +                              return true
 +                      else if pname == "unary -" then
 +                              v.ret(v.new_expr("-{arguments[0]}", ret.as(not null)))
 +                              return true
 +                      else if pname == "unary +" then
 +                              v.ret(arguments[0])
 +                              return true
 +                      else if pname == "*" then
 +                              v.ret(v.new_expr("{arguments[0]} * {arguments[1]}", ret.as(not null)))
 +                              return true
 +                      else if pname == "/" then
 +                              v.ret(v.new_expr("{arguments[0]} / {arguments[1]}", ret.as(not null)))
 +                              return true
 +                      else if pname == "%" then
 +                              v.ret(v.new_expr("{arguments[0]} % {arguments[1]}", ret.as(not null)))
 +                              return true
 +                      else if pname == "lshift" then
 +                              v.ret(v.new_expr("{arguments[0]} << {arguments[1]}", ret.as(not null)))
 +                              return true
 +                      else if pname == "rshift" then
 +                              v.ret(v.new_expr("{arguments[0]} >> {arguments[1]}", ret.as(not null)))
 +                              return true
 +                      else if pname == "==" then
 +                              v.ret(v.equal_test(arguments[0], arguments[1]))
 +                              return true
 +                      else if pname == "!=" then
 +                              var res = v.equal_test(arguments[0], arguments[1])
 +                              v.ret(v.new_expr("!{res}", ret.as(not null)))
 +                              return true
 +                      else if pname == "<" then
 +                              v.ret(v.new_expr("{arguments[0]} < {arguments[1]}", ret.as(not null)))
 +                              return true
 +                      else if pname == ">" then
 +                              v.ret(v.new_expr("{arguments[0]} > {arguments[1]}", ret.as(not null)))
 +                              return true
 +                      else if pname == "<=" then
 +                              v.ret(v.new_expr("{arguments[0]} <= {arguments[1]}", ret.as(not null)))
 +                              return true
 +                      else if pname == ">=" then
 +                              v.ret(v.new_expr("{arguments[0]} >= {arguments[1]}", ret.as(not null)))
 +                              return true
 +                      else if pname == "to_i" then
 +                              v.ret(v.new_expr("(long){arguments[0]}", ret.as(not null)))
 +                              return true
 +                      else if pname == "to_f" then
 +                              v.ret(v.new_expr("(double){arguments[0]}", ret.as(not null)))
 +                              return true
 +                      else if pname == "ascii" then
 +                              v.ret(v.new_expr("{arguments[0]}", ret.as(not null)))
                                return true
                        end
                else if cname == "Bool" then
                        else if pname == "to_i" then
                                v.ret(v.new_expr("(long){arguments[0]}", ret.as(not null)))
                                return true
 +                      else if pname == "to_b" then
 +                              v.ret(v.new_expr("(unsigned char){arguments[0]}", ret.as(not null)))
 +                              return true
                        end
                else if cname == "NativeString" then
                        if pname == "[]" then
 -                              v.ret(v.new_expr("{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 == "[]=" then
 -                              v.add("{arguments[0]}[{arguments[1]}]={arguments[2]};")
 +                              v.add("{arguments[0]}[{arguments[1]}]=(unsigned char){arguments[2]};")
                                return true
                        else if pname == "copy_to" then
                                v.add("memmove({arguments[1]}+{arguments[4]},{arguments[0]}+{arguments[3]},{arguments[2]});")
                                v.ret(v.new_expr("{arguments[0]} + {arguments[1]}", ret.as(not null)))
                                return true
                        else if pname == "new" then
 -                              v.ret(v.new_expr("(char*)nit_alloc({arguments[1]})", ret.as(not null)))
 +                              v.ret(v.new_expr("(unsigned char*)nit_alloc({arguments[1]})", ret.as(not null)))
                                return true
                        end
                else if cname == "NativeArray" then
                        v.ret(v.new_expr("glob_sys", ret.as(not null)))
                        return true
                else if pname == "calloc_string" then
 -                      v.ret(v.new_expr("(char*)nit_alloc({arguments[1]})", ret.as(not null)))
 +                      v.ret(v.new_expr("(unsigned char*)nit_alloc({arguments[1]})", ret.as(not null)))
                        return true
                else if pname == "calloc_array" then
                        v.calloc_array(ret.as(not null), arguments)
@@@ -2884,10 -2794,6 +2893,10 @@@ redef class AIntExp
        redef fun expr(v) do return v.int_instance(self.value.as(not null))
  end
  
 +redef class AByteExpr
 +      redef fun expr(v) do return v.byte_instance(self.value.as(not null))
 +end
 +
  redef class AFloatExpr
        redef fun expr(v) do return v.float_instance("{self.n_float.text}") # FIXME use value, not n_float
  end