nitg: add clean rule in generated Makefile
[nit.git] / src / global_compiler.nit
index e448436..f6e7a8a 100644 (file)
@@ -35,10 +35,13 @@ redef class ToolContext
        # --no-cc
        var opt_no_cc: OptionBool = new OptionBool("Do not invoke C compiler", "--no-cc")
 
+       # --hardening
+       var opt_hardening: OptionBool = new OptionBool("Generate contracts in the C code against bugs in the compiler", "--hardening")
+
        redef init
        do
                super
-               self.option_context.add_option(self.opt_output, self.opt_no_cc)
+               self.option_context.add_option(self.opt_output, self.opt_no_cc, self.opt_hardening)
        end
 end
 
@@ -56,8 +59,8 @@ redef class ModelBuilder
                v.add_decl("#include <string.h>")
 
                # TODO: Better way to activate the GC
-               #v.add_decl("#include <gc/gc.h>")
-               v.add_decl("#define GC_MALLOC(x) calloc(1, (x))")
+               v.add_decl("#include <gc/gc.h>")
+               #v.add_decl("#define GC_MALLOC(x) calloc(1, (x))")
 
                # Declare structure for each live type
 
@@ -175,13 +178,23 @@ redef class ModelBuilder
                makefile.write("all: {outname}\n\n")
 
                var ofiles = new Array[String]
+               # Compile each generated file
                for f in cfiles do
                        var o = f.strip_extension(".c") + ".o"
-                       makefile.write("{o}: {f}\n\t$(CC) $(CFLAGS) -I .nit_compile -I ../clib -c -o {o} {f}\n\n")
+                       makefile.write("{o}: {f}\n\t$(CC) $(CFLAGS) -D NONITCNI -c -o {o} {f}\n\n")
                        ofiles.add(o)
                end
-
-               makefile.write("{outname}: {ofiles.join(" ")} {compiler.extern_bodies.join(" ")}\n\t$(CC) -Wl,--warn-unresolved-symbols $(CFLAGS) $(LDFLAGS) $(LDLIBS) -I .nit_compile -I ../clib -o {outname} {ofiles.join(" ")} {compiler.extern_bodies.join(" ")}\n\n")
+               # Compile each required extern body into a specific .o
+               for f in compiler.extern_bodies do
+                       i += 1
+                       var o = ".nit_compile/{mainmodule.name}.{i}.o"
+                       makefile.write("{o}: {f}\n\t$(CC) $(CFLAGS) -D NONITCNI -c -o {o} {f}\n\n")
+                       ofiles.add(o)
+               end
+               # Link edition
+               makefile.write("{outname}: {ofiles.join(" ")}\n\t$(CC) $(LDFLAGS) $(LDLIBS) -o {outname} {ofiles.join(" ")}\n\n")
+               # Clean
+               makefile.write("clean:\n\trm {ofiles.join(" ")} 2>/dev/null\n\n")
                makefile.close
                self.toolcontext.info("Generated makefile: {makename}", 2)
 
@@ -222,6 +235,9 @@ private class GlobalCompiler
        # The modeulbuilder used to know the model and the AST
        var modelbuilder: ModelBuilder
 
+       # Is hardening asked (see --hardening)
+       fun hardening: Bool do return self.modelbuilder.toolcontext.opt_hardening.value
+
        init(mainmodule: MModule, runtime_type_analysis: RapidTypeAnalysis, modelbuilder: ModelBuilder)
        do
                self.mainmodule = mainmodule
@@ -313,6 +329,7 @@ private class GlobalCompiler
                v.add_decl("/* allocate {mtype} */")
                v.add_decl("{mtype.ctype} NEW_{mtype.c_name}(void) \{")
                var res = v.new_var(mtype)
+               res.is_exact = true
                v.add("{res} = GC_MALLOC(sizeof(struct {mtype.c_name}));")
                v.add("{res}->classid = {self.classid(mtype)};")
 
@@ -350,7 +367,6 @@ private class GlobalCompiler
                if tryfile.file_exists then
                        self.extern_bodies.add(tryfile)
                end
-               #(new OFStream.open("{file.basename("")}._nitni.h")).close
        end
 end
 
@@ -584,9 +600,28 @@ private class RuntimeVariable
        # The current casted type of the variable (as known in Nit)
        var mcasttype: MType
 
+       # If the variable exaclty a mcasttype?
+       # false (usual value) means that the variable is a mcasttype or a subtype.
+       var is_exact: Bool = false
+
        redef fun to_s do return name
 
-       redef fun inspect do return "<{inspect_head} {name}:{mtype}({mcasttype})>"
+       redef fun inspect
+       do
+               var exact_str
+               if self.is_exact then
+                       exact_str = " exact"
+               else
+                       exact_str = ""
+               end
+               var type_str
+               if self.mtype == self.mcasttype then
+                       type_str = "{mtype}{exact_str}"
+               else
+                       type_str = "{mtype}({mcasttype}{exact_str})"
+               end
+               return "<{name}:{type_str}>"
+       end
 end
 
 # Visit the AST to generate the C code.
@@ -788,6 +823,20 @@ private class GlobalCompilerVisitor
 
        private var variables: HashMap[Variable, RuntimeVariable] = new HashMap[Variable, RuntimeVariable]
 
+       # Return an unique and stable identifier associated with an escapemark
+       fun escapemark_name(e: nullable EscapeMark): String
+       do
+               assert e != null
+               if escapemark_names.has_key(e) then return escapemark_names[e]
+               var name = e.name
+               if name == null then name = "label"
+               name = get_name(name)
+               escapemark_names[e] = name
+               return name
+       end
+
+       private var escapemark_names = new HashMap[EscapeMark, String]
+
        # Return a new name based on `s' and unique in the visitor
        fun get_name(s: String): String
        do
@@ -822,6 +871,12 @@ private class GlobalCompilerVisitor
        fun collect_types(recv: RuntimeVariable): Array[MClassType]
        do
                var mtype = recv.mcasttype
+               if recv.is_exact then
+                       assert mtype isa MClassType
+                       assert self.compiler.runtime_type_analysis.live_types.has(mtype)
+                       var types = [mtype]
+                       return types
+               end
                var cache = self.collect_types_cache
                if cache.has_key(mtype) then
                        return cache[mtype]
@@ -864,14 +919,14 @@ private class GlobalCompilerVisitor
                end
 
                if types.is_empty then
-                       self.add("/*BUG: no live types for {args.first.mtype} . {m}*/")
+                       self.add("/*BUG: no live types for {args.first.inspect} . {m}*/")
                        return res
                end
-               self.add("/* send {m} on {args.first}: {args.first.mcasttype} (declared {args.first.mtype}) */")
+               self.add("/* send {m} on {args.first.inspect} */")
                if args.first.mtype.ctype != "val*" then
                        var propdefs = m.lookup_definitions(self.compiler.mainmodule, args.first.mtype)
                        if propdefs.length == 0 then
-                               self.add("/* skip {args.first.mcasttype}, no method {m} */")
+                               self.add("/* skip, no method {m} */")
                                return res
                        end
                        assert propdefs.length == 1
@@ -887,14 +942,14 @@ private class GlobalCompilerVisitor
                                if args[1].mcasttype.ctype == "val*" then
                                        self.add("{res} = ({args[1]} == NULL);")
                                else
-                                       self.add("{res} = 0; /* {args[1]}: {args[1].mcasttype}  cannot be null */")
+                                       self.add("{res} = 0; /* {args[1].inspect} cannot be null */")
                                end
                        else if m.name == "!=" then
                                assert res != null
                                if args[1].mcasttype.ctype == "val*" then
                                        self.add("{res} = ({args[1]} != NULL);")
                                else
-                                       self.add("{res} = 1; /* {args[1]}: {args[1].mcasttype} cannot be null */")
+                                       self.add("{res} = 1; /* {args[1].inspect} cannot be null */")
                                end
                        else
                                self.add_abort("Reciever is null")
@@ -902,6 +957,7 @@ private class GlobalCompilerVisitor
                        self.add "\} else"
                end
                self.add("switch({args.first}->classid) \{")
+               var last = types.last
                var defaultpropdef: nullable MMethodDef = null
                for t in types do
                        var propdefs = m.lookup_definitions(self.compiler.mainmodule, t)
@@ -914,7 +970,11 @@ private class GlobalCompilerVisitor
                                defaultpropdef = propdef
                                continue
                        end
-                       self.add("case {self.compiler.classid(t)}: /* test {t} */")
+                       if not self.compiler.hardening and t == last and defaultpropdef == null then
+                               self.add("default: /* test {t} */")
+                       else
+                               self.add("case {self.compiler.classid(t)}: /* test {t} */")
+                       end
                        var res2 = self.call(propdef, t, args)
                        if res != null then self.assign(res, res2.as(not null))
                        self.add "break;"
@@ -923,7 +983,7 @@ private class GlobalCompilerVisitor
                        self.add("default: /* default is Object */")
                        var res2 = self.call(defaultpropdef, defaultpropdef.mclassdef.bound_mtype, args)
                        if res != null then self.assign(res, res2.as(not null))
-               else
+               else if self.compiler.hardening then
                        self.add("default: /* bug */")
                        self.bugtype(args.first)
                end
@@ -966,7 +1026,7 @@ private class GlobalCompilerVisitor
                                vararg.add(rawargs[i+1])
                        end
                        # FIXME: its it to late to determine the vararg type, this should have been done during a previous analysis
-                       var elttype = m.msignature.parameter_mtypes[vararg_rank]
+                       var elttype = m.msignature.mparameters[vararg_rank].mtype
                        elttype = self.resolve_for(elttype, recv)
                        args.add(self.array_instance(vararg, elttype))
 
@@ -987,7 +1047,7 @@ private class GlobalCompilerVisitor
        do
                var recv = args.first
                for i in [0..m.msignature.arity[ do
-                       var t = m.msignature.parameter_mtypes[i]
+                       var t = m.msignature.mparameters[i].mtype
                        if i == m.msignature.vararg_rank then
                                t = args[i+1].mtype
                        end
@@ -1012,13 +1072,18 @@ private class GlobalCompilerVisitor
                var res = self.new_var(ret)
 
                if types.is_empty then
-                       self.add("/*BUG: no live types for {recv.mtype} . {a}*/")
+                       self.add("/*BUG: no live types for {recv.inspect} . {a}*/")
                        return res
                end
-               self.add("/* read {a} on {recv.mcasttype} */")
+               self.add("/* read {a} on {recv.inspect} */")
                self.add("switch({recv}->classid) \{")
+               var last = types.last
                for t in types do
-                       self.add("case {self.compiler.classid(t)}:")
+                       if not self.compiler.hardening and t == last then
+                               self.add("default: /*{self.compiler.classid(t)}*/")
+                       else
+                               self.add("case {self.compiler.classid(t)}:")
+                       end
                        var recv2 = self.autoadapt(recv, t)
                        var ta = a.intro.static_mtype.as(not null)
                        ta = self.resolve_for(ta, recv2)
@@ -1035,8 +1100,10 @@ private class GlobalCompilerVisitor
                        self.assign(res, res2)
                        self.add("break;")
                end
-               self.add("default: /* Bug */")
-               self.bugtype(recv)
+               if self.compiler.hardening then
+                       self.add("default: /* Bug */")
+                       self.bugtype(recv)
+               end
                self.add("\}")
 
                return res
@@ -1048,21 +1115,28 @@ private class GlobalCompilerVisitor
                var types = self.collect_types(recv)
 
                if types.is_empty then
-                       self.add("/*BUG: no live types for {recv.mtype} . {a}*/")
+                       self.add("/*BUG: no live types for {recv.inspect} . {a}*/")
                        return
                end
-               self.add("/* write {a} on {recv.mcasttype} */")
+               self.add("/* write {a} on {recv.inspect} */")
                self.add("switch({recv}->classid) \{")
+               var last = types.last
                for t in types do
-                       self.add("case {self.compiler.classid(t)}:")
+                       if not self.compiler.hardening and t == last then
+                               self.add("default: /*{self.compiler.classid(t)}*/")
+                       else
+                               self.add("case {self.compiler.classid(t)}:")
+                       end
                        var recv2 = self.autoadapt(recv, t)
                        var ta = a.intro.static_mtype.as(not null)
                        ta = self.resolve_for(ta, recv2)
                        self.add("((struct {t.c_name}*){recv})->{a.intro.c_name} = {self.autobox(value, ta)};")
                        self.add("break;")
                end
-               self.add("default: /* Bug*/")
-               self.bugtype(recv)
+               if self.compiler.hardening then
+                       self.add("default: /* Bug*/")
+                       self.bugtype(recv)
+               end
                self.add("\}")
        end
 
@@ -1074,6 +1148,7 @@ private class GlobalCompilerVisitor
                        debug "problem: {mtype} was detected dead"
                end
                var res = self.new_expr("NEW_{mtype.c_name}()", mtype)
+               res.is_exact = true
                return res
        end
 
@@ -1085,7 +1160,7 @@ private class GlobalCompilerVisitor
 
                var res = self.new_var(bool_type)
 
-               self.add("/* isa {mtype} on {value.mcasttype} */")
+               self.add("/* isa {mtype} on {value.inspect} */")
                if value.mcasttype isa MNullableType then
                        self.add("if ({value} == NULL) \{")
                        if mtype isa MNullableType then
@@ -1168,6 +1243,7 @@ private class GlobalCompilerVisitor
                var res = self.init_instance(self.get_class("Array").get_mtype([elttype]))
                self.add("\{ /* {res} = array_instance Array[{elttype}] */")
                var nat = self.new_var(self.get_class("NativeArray").get_mtype([elttype]))
+               nat.is_exact = true
                self.add("{nat} = GC_MALLOC({array.length} * sizeof({elttype.ctype}));")
                for i in [0..array.length[ do
                        var r = self.autobox(array[i], elttype)
@@ -1183,12 +1259,22 @@ private class GlobalCompilerVisitor
        # Generate a string value
        fun string_instance(string: String): RuntimeVariable
        do
+               var mtype = self.get_class("String").mclass_type
+               var name = self.get_name("varonce")
+               self.add_decl("static {mtype.ctype} {name};")
+               var res = self.new_var(mtype)
+               self.add("if ({name}) \{")
+               self.add("{res} = {name};")
+               self.add("\} else \{")
                var nat = self.new_var(self.get_class("NativeString").mclass_type)
                self.add("{nat} = \"{string.escape_to_c}\";")
-               var res = self.init_instance(self.get_class("String").mclass_type)
+               var res2 = self.init_instance(mtype)
+               self.add("{res} = {res2};")
                var length = self.int_instance(string.length)
                self.send(self.get_property("with_native", res.mtype), [res, nat, length])
                self.check_init_instance(res)
+               self.add("{name} = {res};")
+               self.add("\}")
                return res
        end
 
@@ -1197,9 +1283,9 @@ private class GlobalCompilerVisitor
        fun add_abort(message: String)
        do
                if self.current_node != null and self.current_node.location.file != null then
-                       self.add("fprintf(stderr, \"%s (%s:%d)\\n\", \"{message.escape_to_c}\", \"{self.current_node.location.file.filename.escape_to_c}\", {current_node.location.line_start});")
+                       self.add("fprintf(stderr, \"Runtime error: %s (%s:%d)\\n\", \"{message.escape_to_c}\", \"{self.current_node.location.file.filename.escape_to_c}\", {current_node.location.line_start});")
                else
-                       self.add("fprintf(stderr, \"%s\\n\", \"{message.escape_to_c}\");")
+                       self.add("fprintf(stderr, \"Runtime error: %s\\n\", \"{message.escape_to_c}\");")
                end
                self.add("exit(1);")
        end
@@ -1251,7 +1337,8 @@ redef class MMethodDef
                        var npropdef = modelbuilder.mpropdef2npropdef[self]
                        return npropdef.can_inline
                else if self.mproperty.name == "init" then
-                       return false
+                       # Automatic free init is always inlined since it is empty or contains only attribtes assigments
+                       return true
                else
                        abort
                end
@@ -1283,6 +1370,9 @@ redef class MMethodDef
 
                var v = new GlobalCompilerVisitor(compiler)
                var selfvar = new RuntimeVariable("self", recv, recv)
+               if compiler.runtime_type_analysis.live_types.has(recv) then
+                       selfvar.is_exact = true
+               end
                var arguments = new Array[RuntimeVariable]
                var frame = new Frame(v, self, recv, arguments)
                v.frame = frame
@@ -1307,7 +1397,7 @@ redef class MMethodDef
                comment.append("(self: {recv}")
                arguments.add(selfvar)
                for i in [0..self.msignature.arity[ do
-                       var mtype = self.msignature.parameter_mtypes[i]
+                       var mtype = self.msignature.mparameters[i].mtype
                        if i == self.msignature.vararg_rank then
                                mtype = v.get_class("Array").get_mtype([mtype])
                        end
@@ -1353,7 +1443,7 @@ end
 redef class APropdef
        private fun compile_to_c(v: GlobalCompilerVisitor, mpropdef: MMethodDef, arguments: Array[RuntimeVariable])
        do
-               v.add("printf(\"Not implemented {class_name} {mpropdef} at {location.to_s}\\n\");")
+               v.add("printf(\"NOT YET IMPLEMENTED {class_name} {mpropdef} at {location.to_s}\\n\");")
                debug("Not yet implemented")
        end
 
@@ -1389,7 +1479,7 @@ redef class AConcreteMethPropdef
                var nblock = self.n_block
                if nblock == null then return true
                if (mpropdef.mproperty.name == "==" or mpropdef.mproperty.name == "!=") and mpropdef.mclassdef.mclass.name == "Object" then return true
-               #if nblock isa ABlockExpr and nblock.n_expr.length == 1 then return true
+               if nblock isa ABlockExpr and nblock.n_expr.length == 0 then return true
                return false
        end
 end
@@ -1657,7 +1747,7 @@ redef class AInternMethPropdef
                        end
                        return
                end
-               v.add("printf(\"Not implemented {class_name}:{mpropdef} at {location.to_s}\\n\");")
+               v.add("printf(\"NOT IMPLEMENTED {class_name}:{mpropdef} at {location.to_s}\\n\");")
                debug("Not implemented {mpropdef}")
        end
 end
@@ -1768,13 +1858,23 @@ redef class AClassdef
        end
 end
 
+redef class ADeferredMethPropdef
+       redef fun compile_to_c(v, mpropdef, arguments)
+       do
+               v.add("printf(\"Not implemented {class_name} {mpropdef} at {location.to_s}\\n\");")
+               v.add("exit(1);")
+       end
+
+       redef fun can_inline do return true
+end
+
 redef class AExpr
        # Try to compile self as an expression
        # Do not call this method directly, use `v.expr' instead
        private fun expr(v: GlobalCompilerVisitor): nullable RuntimeVariable
        do
                debug("Unimplemented expr {class_name}")
-               v.add("printf(\"Not implemented {class_name}:{location.to_s}\\n\");")
+               v.add("printf(\"NOT YET IMPLEMENTED {class_name}:{location.to_s}\\n\");")
                var mtype = self.mtype
                if mtype == null then
                        return null
@@ -1856,14 +1956,14 @@ end
 redef class AContinueExpr
        redef fun stmt(v)
        do
-               v.add("goto CONTINUE_{self.escapemark.object_id};")
+               v.add("goto CONTINUE_{v.escapemark_name(self.escapemark)};")
        end
 end
 
 redef class ABreakExpr
        redef fun stmt(v)
        do
-               v.add("goto BREAK_{self.escapemark.object_id};")
+               v.add("goto BREAK_{v.escapemark_name(self.escapemark)};")
        end
 end
 
@@ -1919,7 +2019,7 @@ redef class ADoExpr
                v.stmt(self.n_block)
                var escapemark = self.escapemark
                if escapemark != null then
-                       v.add("BREAK_{escapemark.object_id}: (void)0;")
+                       v.add("BREAK_{v.escapemark_name(escapemark)}: (void)0;")
                end
        end
 end
@@ -1931,9 +2031,9 @@ redef class AWhileExpr
                var cond = v.expr_bool(self.n_expr)
                v.add("if (!{cond}) break;")
                v.stmt(self.n_block)
-               v.add("CONTINUE_{escapemark.object_id}: (void)0;")
+               v.add("CONTINUE_{v.escapemark_name(escapemark)}: (void)0;")
                v.add("\}")
-               v.add("BREAK_{escapemark.object_id}: (void)0;")
+               v.add("BREAK_{v.escapemark_name(escapemark)}: (void)0;")
        end
 end
 
@@ -1942,9 +2042,9 @@ redef class ALoopExpr
        do
                v.add("for(;;) \{")
                v.stmt(self.n_block)
-               v.add("CONTINUE_{escapemark.object_id}: (void)0;")
+               v.add("CONTINUE_{v.escapemark_name(escapemark)}: (void)0;")
                v.add("\}")
-               v.add("BREAK_{escapemark.object_id}: (void)0;")
+               v.add("BREAK_{v.escapemark_name(escapemark)}: (void)0;")
        end
 end
 
@@ -1958,14 +2058,25 @@ redef class AForExpr
                var ok = v.send(v.get_property("is_ok", it.mtype), [it])
                assert ok != null
                v.add("if(!{ok}) break;")
-               var i = v.send(v.get_property("item", it.mtype), [it])
-               assert i != null
-               v.assign(v.variable(variables.first), i)
+               if self.variables.length == 1 then
+                       var i = v.send(v.get_property("item", it.mtype), [it])
+                       assert i != null
+                       v.assign(v.variable(variables.first), i)
+               else if self.variables.length == 2 then
+                       var i = v.send(v.get_property("key", it.mtype), [it])
+                       assert i != null
+                       v.assign(v.variable(variables[0]), i)
+                       i = v.send(v.get_property("item", it.mtype), [it])
+                       assert i != null
+                       v.assign(v.variable(variables[1]), i)
+               else
+                       abort
+               end
                v.stmt(self.n_block)
-               v.add("CONTINUE_{escapemark.object_id}: (void)0;")
+               v.add("CONTINUE_{v.escapemark_name(escapemark)}: (void)0;")
                v.send(v.get_property("next", it.mtype), [it])
                v.add("\}")
-               v.add("BREAK_{escapemark.object_id}: (void)0;")
+               v.add("BREAK_{v.escapemark_name(escapemark)}: (void)0;")
        end
 end
 
@@ -2093,6 +2204,7 @@ redef class ASuperstringExpr
        do
                var array = new Array[RuntimeVariable]
                for ne in self.n_exprs do
+                       if ne isa AStringFormExpr and ne.value == "" then continue # skip empty sub-strings
                        var i = v.expr(ne, null)
                        array.add(i)
                end
@@ -2212,7 +2324,7 @@ redef class ASendExpr
        do
                var recv = v.expr(self.n_expr, null)
                var args = [recv]
-               for a in compute_raw_arguments do
+               for a in self.raw_arguments.as(not null) do
                        args.add(v.expr(a, null))
                end
                var mproperty = self.mproperty.as(not null)
@@ -2225,7 +2337,7 @@ redef class ASendReassignFormExpr
        do
                var recv = v.expr(self.n_expr, null)
                var args = [recv]
-               for a in compute_raw_arguments do
+               for a in self.raw_arguments.as(not null) do
                        args.add(v.expr(a, null))
                end
                var value = v.expr(self.n_value, null)