nitj: implement static and polymorphic sends
[nit.git] / src / compiler / java_compiler.nit
index 2380705..141a6ef 100644 (file)
@@ -209,6 +209,7 @@ class JavaCompiler
        fun do_compilation do
                # compile java classes used to represents the runtime model of the program
                rt_model.compile_rtmodel(self)
+               compile_box_kinds
 
                # compile class structures
                compile_mclasses_to_java
@@ -220,6 +221,21 @@ class JavaCompiler
                modelbuilder.toolcontext.info("NOT YET IMPLEMENTED", 0)
        end
 
+       # Prepare the boxes used to represent Java primitive types
+       fun compile_box_kinds do
+               # Collect all bas box class
+               # FIXME: this is not completely fine with a separate compilation scheme
+               for classname in ["Int", "Bool", "Byte", "Char", "Float"] do
+                       var classes = mainmodule.model.get_mclasses_by_name(classname)
+                       if classes == null then continue
+                       assert classes.length == 1 else print classes.join(", ")
+                       box_kinds.add(classes.first.mclass_type)
+               end
+       end
+
+       # Types of boxes used to represent Java primitive types
+       var box_kinds = new Array[MClassType]
+
        # Generate a `RTClass` for each `MClass` found in model
        #
        # This is a global phase because we need to know all the program to build
@@ -296,7 +312,7 @@ class JavaCompilerVisitor
                else
                        var name = get_name("var_{variable.name}")
                        var mtype = variable.declared_type.as(not null)
-                       # TODO mtype = self.anchor(mtype)
+                       mtype = anchor(mtype)
                        var res = decl_var(name, mtype)
                        variables[variable] = res
                        return res
@@ -306,13 +322,14 @@ class JavaCompilerVisitor
        # Return a new uninitialized local RuntimeVariable with `name`
        fun decl_var(name: String, mtype: MType): RuntimeVariable do
                var res = new RuntimeVariable(name, mtype, mtype)
+               res.is_boxed = not mtype.is_java_primitive
                add("{mtype.java_type} {name} /* : {mtype} */;")
                return res
        end
 
        # Return a new uninitialized local RuntimeVariable
        fun new_var(mtype: MType): RuntimeVariable do
-               # TODO mtype = self.anchor(mtype)
+               mtype = anchor(mtype)
                var name = self.get_name("var")
                return decl_var(name, mtype)
        end
@@ -329,6 +346,222 @@ class JavaCompilerVisitor
                return res
        end
 
+       # Calls handling
+
+       # Compile a call within a callsite
+       fun compile_callsite(callsite: CallSite, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do
+               var initializers = callsite.mpropdef.initializers
+               if not initializers.is_empty then
+                       var recv = arguments.first
+
+                       var i = 1
+                       for p in initializers do
+                               if p isa MMethod then
+                                       var args = [recv]
+                                       var msignature = p.intro.msignature
+                                       if msignature != null then
+                                               for x in msignature.mparameters do
+                                                       args.add arguments[i]
+                                                       i += 1
+                                               end
+                                       end
+                                       send(p, args)
+                               else if p isa MAttribute then
+                                       info("NOT YET IMPLEMENTED {class_name}::compile_callsite for MAttribute `{p}`")
+                                       #self.write_attribute(p, recv, arguments[i])
+                                       i += 1
+                               else abort
+                       end
+                       assert i == arguments.length
+
+                       return send(callsite.mproperty, [recv])
+               end
+
+               return send(callsite.mproperty, arguments)
+       end
+
+       # Evaluate `args` as expressions in the call of `mpropdef` on `recv`.
+       #
+       # This method is used to manage varargs in signatures and returns the real array
+       # of runtime variables to use in the call.
+       fun varargize(mpropdef: MMethodDef, map: nullable SignatureMap, recv: RuntimeVariable, args: SequenceRead[AExpr]): Array[RuntimeVariable] do
+               var msignature = mpropdef.new_msignature or else mpropdef.msignature.as(not null)
+               var res = new Array[RuntimeVariable]
+               res.add(recv)
+
+               if msignature.arity == 0 then return res
+
+               if map == null then
+                       assert args.length == msignature.arity
+                       for ne in args do
+                               res.add expr(ne, null)
+                       end
+                       return res
+               end
+
+               # Eval in order of arguments, not parameters
+               var exprs = new Array[RuntimeVariable].with_capacity(args.length)
+               for ne in args do
+                       exprs.add expr(ne, null)
+               end
+
+               # Fill `res` with the result of the evaluation according to the mapping
+               for i in [0..msignature.arity[ do
+                       var param = msignature.mparameters[i]
+                       var j = map.map.get_or_null(i)
+                       if j == null then
+                               # default value
+                               res.add(null_instance)
+                               continue
+                       end
+                       if param.is_vararg and map.vararg_decl > 0 then
+                               var vararg = exprs.sub(j, map.vararg_decl)
+                               var elttype = param.mtype
+                               var arg = self.vararg_instance(mpropdef, recv, vararg, elttype)
+                               res.add(arg)
+                               continue
+                       end
+                       res.add exprs[j]
+               end
+               return res
+       end
+
+       #  Generate a static call on a method definition (no receiver needed).
+       fun static_call(mmethoddef: MMethodDef, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do
+               var res: nullable RuntimeVariable
+               var ret = mmethoddef.msignature.as(not null).return_mtype
+               if ret == null then
+                       res = null
+               else
+                       ret = ret.resolve_for(mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.mmodule, true)
+                       res = self.new_var(ret)
+               end
+
+               # Autobox arguments
+               adapt_signature(mmethoddef, arguments)
+
+               var rt_name = mmethoddef.rt_name
+               if res == null then
+                       add("{rt_name}.get{rt_name}().exec(new RTVal[]\{{arguments.join(",")}\});")
+                       return null
+               end
+               var ress = new_expr("{rt_name}.get{rt_name}().exec(new RTVal[]\{{arguments.join(",")}\});", compiler.mainmodule.object_type)
+               assign(res, ress)
+               return res
+       end
+
+       #  Generate a polymorphic send for `method` with `arguments`
+       fun send(mmethod: MMethod, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do
+               # Polymorphic send
+               return table_send(mmethod, arguments)
+       end
+
+
+       # Handle common special cases before doing the effective method invocation
+       # This methods handle the `==` and `!=` methods and the case of the null receiver.
+       # Note: a { is open in the generated C, that enclose and protect the effective method invocation.
+       # Client must not forget to close the } after them.
+       #
+       # The value returned is the result of the common special cases.
+       # If not null, client must compile it with the result of their own effective method invocation.
+       #
+       # If `before_send` can shortcut the whole message sending, a dummy `if(0){`
+       # is generated to cancel the effective method invocation that will follow
+       # TODO: find a better approach
+       private fun before_send(res: nullable RuntimeVariable, mmethod: MMethodDef, arguments: Array[RuntimeVariable]) do
+               var bool_type = compiler.mainmodule.bool_type
+               var recv = arguments.first
+               var consider_null = mmethod.name == "==" or mmethod.name == "!=" or mmethod.name == "is_same_instance"
+               if recv.mcasttype isa MNullableType or recv.mcasttype isa MNullType then
+                       add("if ({recv} == null || {recv}.is_null()) \{")
+                       if mmethod.name == "==" or mmethod.name == "is_same_instance" then
+                               if res == null then res = new_var(bool_type)
+                               var arg = arguments[1]
+                               if arg.mcasttype isa MNullableType then
+                                       add("{res} = ({arg} == null || {arg}.is_null());")
+                               else if arg.mcasttype isa MNullType then
+                                       add("{res} = true; /* is null */")
+                               else
+                                       add("{res} = false; /* {arg.inspect} cannot be null */")
+                               end
+                       else if mmethod.name == "!=" then
+                               if res == null then res = new_var(bool_type)
+                               # res = self.new_var(bool_type)
+                               var arg = arguments[1]
+                               if arg.mcasttype isa MNullableType then
+                                       add("{res} = ({arg} != null && !{arg}.is_null());")
+                               else if arg.mcasttype isa MNullType then
+                                       add("{res} = false; /* is null */")
+                               else
+                                       add("{res} = true; /* {arg.inspect} cannot be null */")
+                               end
+                       else
+                               add_abort("Receiver is null")
+                               ret(null_instance)
+                       end
+                       add("\} else \{")
+               else
+                       add "\{"
+                       add "/* recv ({recv}) cannot be null since it's a {recv.mcasttype}"
+               end
+               if consider_null then
+                       var arg = arguments[1]
+                       if arg.mcasttype isa MNullType then
+                               if res == null then res = new_var(bool_type)
+                               if mmethod.name == "!=" then
+                                       add("{res} = true; /* arg is null and recv is not */")
+                               else # `==` and `is_same_instance`
+                                       add("{res} = false; /* arg is null but recv is not */")
+                               end
+                               add("\}") # closes the null case
+                               add("if (false) \{") # what follow is useless, Javac will drop it
+                       end
+               end
+       end
+
+       # Perform a method call through vft
+       private fun table_send(mmethod: TableCallable, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do
+               var mdef: MMethodDef
+               var name: String
+               if mmethod isa MMethod then
+                       mdef = mmethod.intro
+                       name = mmethod.full_name
+               else if mmethod isa MMethodDef then
+                       mdef = mmethod
+                       name = mmethod.full_name
+               else
+                       abort
+               end
+
+               var recv = arguments.first
+               var rect = mdef.mclassdef.bound_mtype
+               var msignature = mdef.msignature.as(not null)
+               msignature = msignature.resolve_for(rect, rect, compiler.mainmodule, true)
+               adapt_signature(mdef, arguments)
+
+               var res: nullable RuntimeVariable
+               var ret = msignature.return_mtype
+               if ret == null then
+                       res = null
+               else
+                       res = self.new_var(ret)
+               end
+
+               before_send(res, mdef, arguments)
+
+               add "/* concrete call to {mdef} */"
+               if res != null then
+                       var ress = new_expr("{recv}.rtclass.vft.get(\"{name}\").exec(new RTVal[]\{{arguments.join(",")}\});", compiler.mainmodule.object_type)
+                       assign(res, ress)
+               else
+                       add("{recv}.rtclass.vft.get(\"{name}\").exec(new RTVal[]\{{arguments.join(",")}\});")
+               end
+
+               add("\}") # closes the null case
+
+               return res
+       end
+
        # Code generation
 
        # Add a line (will be suffixed by `\n`)
@@ -356,7 +589,12 @@ class JavaCompilerVisitor
                if nexpr.mtype != null then
                        res = nexpr.expr(self)
                end
-               assert res != null
+
+               if mtype != null then
+                       mtype = anchor(mtype)
+                       res = autobox(res, mtype)
+               end
+
                current_node = old
                return res
        end
@@ -364,8 +602,7 @@ class JavaCompilerVisitor
        # Correctly assign a left and a right value
        # Boxing and unboxing is performed if required
        fun assign(left, right: RuntimeVariable) do
-               # TODO right = autobox(right, left.mtype)
-               add("{left} = {right};")
+               add("{left} = {autobox(right, left.mtype)};")
        end
 
        # Return a new local RuntimeVariable initialized with the Java expression `jexpr`.
@@ -390,6 +627,113 @@ class JavaCompilerVisitor
                add("System.exit(1);")
        end
 
+       # Types handling
+
+       # Anchor a type to the main module and the current receiver
+       fun anchor(mtype: MType): MType do
+               if not mtype.need_anchor then return mtype
+               return mtype.anchor_to(compiler.mainmodule, frame.as(not null).receiver)
+       end
+
+       # Adapt the arguments of a method according to targetted `MMethodDef`
+       fun adapt_signature(m: MMethodDef, args: Array[RuntimeVariable]) do
+               var msignature = m.msignature.as(not null).resolve_for(
+                       m.mclassdef.bound_mtype,
+                       m.mclassdef.bound_mtype,
+                       m.mclassdef.mmodule, true)
+               args.first = autobox(args.first, compiler.mainmodule.object_type)
+               for i in [0..msignature.arity[ do
+                       args[i+1] = autobox(args[i + 1], compiler.mainmodule.object_type)
+               end
+       end
+
+       # Box primitive `value` to `mtype`.
+       private fun box(value: RuntimeVariable, mtype: MType): RuntimeVariable do
+               if value.is_boxed then return value
+               var obj_type = compiler.mainmodule.object_type
+               if value.mtype isa MNullType then
+                       return new_expr("new RTVal(null, null)", compiler.mainmodule.model.null_type)
+               end
+               var mbox = value.mtype.as(MClassType).mclass
+               return new_expr("new RTVal({mbox.rt_name}.get{mbox.rt_name}(), {value})", obj_type)
+       end
+
+       # Unbox primitive `value` to `mtype`.
+       private fun unbox(value: RuntimeVariable, mtype: MType): RuntimeVariable do
+               if not value.is_boxed then return value
+               if not mtype.is_java_primitive then return value
+               if compiler.box_kinds.has(mtype) then
+                       return new_expr("({mtype.java_type}){value}.value", mtype)
+               else
+                       info "NOT YET IMPLEMENTED unbox for {value} ({mtype})"
+                       abort
+               end
+       end
+
+       # Box or unbox primitive `value` to `mtype` if needed.
+       private fun autobox(value: RuntimeVariable, mtype: MType): RuntimeVariable do
+               if mtype.is_java_primitive then return unbox(value, mtype)
+               return box(value, mtype)
+       end
+
+       # Can this `value` be a primitive Java value?
+       private fun can_be_primitive(value: RuntimeVariable): Bool do
+               var t = value.mcasttype.undecorate
+               if not t isa MClassType then return false
+               var k = t.mclass.kind
+               return k == interface_kind or t.is_java_primitive
+       end
+
+       # Native instances
+
+       # Generate an integer value
+       fun int_instance(value: Int): RuntimeVariable do
+               var t = compiler.mainmodule.int_type
+               return new RuntimeVariable(value.to_s, t, t)
+       end
+
+       # Generate a byte value
+       fun byte_instance(value: Byte): RuntimeVariable do
+               var t = compiler.mainmodule.byte_type
+               return new RuntimeVariable(value.to_s, t, t)
+       end
+
+       # Generate a char value
+       fun char_instance(value: Char): RuntimeVariable do
+               var t = compiler.mainmodule.char_type
+               return new RuntimeVariable("'{value.to_s.escape_to_c}'", t, t)
+       end
+
+       # Generate a float value
+       #
+       # FIXME pass a Float, not a string
+       fun float_instance(value: String): RuntimeVariable do
+               var t = compiler.mainmodule.float_type
+               return new RuntimeVariable(value.to_s, t, t)
+       end
+
+       # Generate an integer value
+       fun bool_instance(value: Bool): RuntimeVariable do
+               var t = compiler.mainmodule.bool_type
+               return new RuntimeVariable(value.to_s, t, t)
+       end
+
+       # Generate the `null` value
+       fun null_instance: RuntimeVariable do
+               var t = compiler.mainmodule.model.null_type
+               return new RuntimeVariable("null", t, t)
+       end
+
+       # Get an instance of a array for a vararg
+       fun vararg_instance(mpropdef: MPropDef, recv: RuntimeVariable, varargs: Array[RuntimeVariable], elttype: MType): RuntimeVariable do
+               # TODO handle dynamic types
+               info("NOT YET IMPLEMENTED vararg_instance")
+               return null_instance
+               # TODO return array_instance(varargs, elttype)
+       end
+
+       # Utils
+
        # Display a info message
        fun info(str: String) do compiler.modelbuilder.toolcontext.info(str, 0)
 end
@@ -648,7 +992,16 @@ redef class MClass
        end
 end
 
+# Used as a common type between MMethod and MMethodDef for `table_send`
+private interface TableCallable
+end
+
+redef class MMethod
+       super TableCallable
+end
+
 redef class MMethodDef
+       super TableCallable
 
        # Runtime name
        private fun rt_name: String do
@@ -755,6 +1108,15 @@ redef class ABlockExpr
        end
 end
 
+redef class ASendExpr
+       redef fun expr(v) do
+               var recv = v.expr(n_expr, null)
+               var callsite = callsite.as(not null)
+               var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, raw_arguments)
+               return v.compile_callsite(callsite, args)
+       end
+end
+
 redef class ASelfExpr
        redef fun expr(v) do return v.frame.as(not null).arguments.first
 end
@@ -763,6 +1125,61 @@ redef class AImplicitSelfExpr
        redef fun expr(v) do return v.frame.as(not null).arguments.first
 end
 
+redef class AVardeclExpr
+       redef fun stmt(v) do
+               var variable = self.variable.as(not null)
+               var ne = self.n_expr
+               var decl = v.variable(variable)
+               if ne != null then
+                       var i = v.expr(ne, variable.declared_type)
+                       v.assign(decl, i)
+               end
+       end
+end
+
+redef class AVarExpr
+       redef fun expr(v) do
+               return v.variable(self.variable.as(not null))
+       end
+end
+
+redef class AVarAssignExpr
+       redef fun expr(v) do
+               var variable = self.variable.as(not null)
+               var i = v.expr(self.n_value, variable.declared_type)
+               v.assign(v.variable(variable), i)
+               return i
+       end
+end
+
+redef class AIntExpr
+       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
+
+redef class ACharExpr
+       redef fun expr(v) do return v.char_instance(self.value.as(not null))
+end
+
+redef class ATrueExpr
+       redef fun expr(v) do return v.bool_instance(true)
+end
+
+redef class AFalseExpr
+       redef fun expr(v) do return v.bool_instance(false)
+end
+
+redef class ANullExpr
+       redef fun expr(v) do return v.null_instance
+end
+
 redef class AAbortExpr
        redef fun stmt(v) do v.add_abort("Aborted")
 end