Callref expression support for Separate Compiler.
authorLouis-Vincent Boudreault <lv.boudreault95@gmail.com>
Thu, 12 Sep 2019 15:01:50 +0000 (11:01 -0400)
committerLouis-Vincent Boudreault <lv.boudreault95@gmail.com>
Thu, 10 Oct 2019 14:40:04 +0000 (10:40 -0400)
- Added test case in `src/compiler/test_callref.nit`
- Updated the API of `AbstractCompiler` to support routine reference
call and instantiation services.
- Implemented routine reference (callrefs) handling for separate
compiler

Signed-off-by: Louis-Vincent Boudreault <lv.boudreault95@gmail.com>

src/compiler/abstract_compiler.nit
src/compiler/separate_compiler.nit
src/rapid_type_analysis.nit
src/test_callref.nit [new file with mode: 0644]

index b512cdd..669159f 100644 (file)
@@ -625,6 +625,20 @@ abstract class AbstractCompiler
        # The targeted specific platform
        var target_platform: Platform is noinit
 
+        # All methods who already has a callref_thunk generated for
+        var compiled_callref_thunk = new HashSet[MMethodDef]
+
+        var all_routine_types_name: Set[String] do
+                var res = new HashSet[String]
+                for name in ["Fun", "Proc", "FunRef", "ProcRef"] do
+                        # Currently there's 20 arity per func type
+                        for i in [0..20[ do
+                                res.add("{name}{i}")
+                        end
+                end
+                return res
+        end
+
        init
        do
                self.realmainmodule = mainmodule
@@ -1377,6 +1391,12 @@ abstract class AbstractCompilerVisitor
        # The method is unsafe and is just a direct wrapper for the specific implementation of native arrays
        fun native_array_set(native_array: RuntimeVariable, index: Int, value: RuntimeVariable) is abstract
 
+        # Instantiate a new routine pointer
+        fun routine_ref_instance(routine_mclass_type: MClassType, recv: RuntimeVariable, mmethoddef: MMethodDef): RuntimeVariable is abstract
+
+        # Call the underlying referenced function
+        fun routine_ref_call(mmethoddef: MMethodDef, args: Array[RuntimeVariable]) is abstract
+
        # Allocate `size` bytes with the low_level `nit_alloc` C function
        #
        # This method can be redefined to inject statistic or tracing code.
@@ -2524,6 +2544,19 @@ redef class MClassType
        end
 end
 
+redef class MSignature
+        fun change_all_mtype_for(mtype: MType): MSignature
+        do
+                var ps = new Array[MParameter]
+                for p in mparameters do
+                        ps.push(new MParameter(p.name, mtype, p.is_vararg))
+                end
+                var ret: nullable MType = null
+                if return_mtype != null then ret = mtype
+                return new MSignature(ps, ret)
+        end
+end
+
 redef class MPropDef
        type VISITOR: AbstractCompilerVisitor
 end
@@ -2689,10 +2722,15 @@ redef class AMethPropdef
                var pname = mpropdef.mproperty.name
                var cname = mpropdef.mclassdef.mclass.name
                var ret = mpropdef.msignature.return_mtype
-               if ret != null then
+                var compiler = v.compiler
+                # WARNING: we must not resolve the return type when it's a functional type.
+                # Otherwise, we get a compile error exactly here. This weird behavior doesn't affect
+                # the inner mecanics of callref since the return type is already solved by
+                # `routine_ref_call`
+                if ret != null and not compiler.all_routine_types_name.has(cname) then
                        ret = v.resolve_for(ret, arguments.first)
                end
-               if pname != "==" and pname != "!=" then
+               if pname != "==" and pname != "!=" and pname != "call" and not compiler.all_routine_types_name.has(cname) then
                        v.adapt_signature(mpropdef, arguments)
                        v.unbox_signature_extern(mpropdef, arguments)
                end
@@ -3455,6 +3493,9 @@ redef class AMethPropdef
                                v.ret(v.new_expr("~{arguments[0]}", ret.as(not null)))
                                return true
                        end
+                else if compiler.all_routine_types_name.has(cname) then
+                        v.routine_ref_call(mpropdef, arguments)
+                        return true
                end
                if pname == "exit" then
                        v.add("exit((int){arguments[1]});")
@@ -4375,8 +4416,9 @@ end
 redef class ACallrefExpr
         redef fun expr(v)
         do
-                v.add_abort("NOT YET IMPLEMENTED callref expressions.")
-                return null
+                var recv = v.expr(self.n_expr, null)
+                var res = v.routine_ref_instance(mtype.as(MClassType), recv, callsite.as(not null).mpropdef)
+                return res
         end
 end
 
index f9d1444..f06e61f 100644 (file)
@@ -149,6 +149,7 @@ class SeparateCompiler
        private var type_ids: Map[MType, Int] is noinit
        private var type_colors: Map[MType, Int] is noinit
        private var opentype_colors: Map[MType, Int] is noinit
+        private var thunks_to_compile: Set[SeparateRuntimeFunction] = new HashSet[SeparateRuntimeFunction]
 
        init do
                var file = new_file("nit.common")
@@ -195,6 +196,15 @@ class SeparateCompiler
                compiler.compile_types
        end
 
+        fun thunk_todo(thunk: SeparateRuntimeFunction)
+        do
+                # Concrete instance of `SeparateRuntimeFunction` are already
+                # handled by the compiler. Avoid duplicate compilation.
+                if thunk isa SeparateThunkFunction then
+                        thunks_to_compile.add(thunk)
+                end
+        end
+
        # Color and compile type structures and cast information
        fun compile_types
        do
@@ -273,8 +283,7 @@ class SeparateCompiler
                        return self.box_kinds[self.mainmodule.pointer_type.mclass]
                else
                        return self.box_kinds[mclass]
-               end
-
+                end
        end
 
        fun compile_color_consts(colors: Map[Object, Int]) do
@@ -414,7 +423,6 @@ class SeparateCompiler
                        attr_tables[mclass] = attr_colorer.build_layout(mclass)
                end
 
-
        end
 
        # colorize live types of the program
@@ -506,7 +514,6 @@ class SeparateCompiler
                return tables
        end
 
-
        private fun compute_type_test_layouts(mtypes: Set[MClassType], cast_types: Set[MType]) do
                # Group cast_type by their classes
                var bucklets = new HashMap[MClass, Set[MType]]
@@ -641,6 +648,15 @@ class SeparateCompiler
                                end
                        end
                end
+                var compiled_thunks = new Array[SeparateRuntimeFunction]
+                # Compile thunks here to write them in the same module they are declared.
+                for thunk in thunks_to_compile do
+                        if thunk.mmethoddef.mclassdef.mmodule == mmodule then
+                                thunk.compile_to_c(self)
+                                compiled_thunks.add(thunk)
+                        end
+                end
+                thunks_to_compile.remove_all(compiled_thunks)
                self.mainmodule = old_module
        end
 
@@ -938,6 +954,29 @@ class SeparateCompiler
                        v.add("return (val*){res};")
                        v.add("\}")
                        return
+                else if mclass.name == "RoutineRef" then
+                        self.header.add_decl("struct instance_{c_name} \{")
+                        self.header.add_decl("const struct type *type;")
+                        self.header.add_decl("const struct class *class;")
+                        self.header.add_decl("val* recv;")
+                        self.header.add_decl("nitmethod_t method;")
+                        self.header.add_decl("\};")
+
+                        self.provide_declaration("NEW_{c_name}", "{mtype.ctype} NEW_{c_name}(val* recv, nitmethod_t method, const struct class* class, const struct type* type);")
+                        v.add_decl("/* allocate {mtype} */")
+                        v.add_decl("{mtype.ctype} NEW_{c_name}(val* recv, nitmethod_t method, const struct class* class, const struct type* type)\{")
+                        var res = v.get_name("self")
+                        v.add_decl("struct instance_{c_name} *{res};")
+                        var alloc = v.nit_alloc("sizeof(struct instance_{c_name})", mclass.full_name)
+                        v.add("{res} = {alloc};")
+                        v.add("{res}->type = type;")
+                        hardening_live_type(v, "type")
+                        v.add("{res}->class = class;")
+                        v.add("{res}->recv = recv;")
+                        v.add("{res}->method = method;")
+                        v.add("return (val*){res};")
+                        v.add("\}")
+                        return
                else if mtype.mclass.kind == extern_kind and mtype.mclass.name != "CString" then
                        # Is an extern class (other than Pointer and CString)
                        # Pointer is caught in a previous `if`, and CString is internal
@@ -2149,6 +2188,120 @@ class SeparateCompilerVisitor
                self.add("{recv}[{i}]={val};")
        end
 
+        redef fun routine_ref_instance(routine_type, recv, mmethoddef)
+        do
+                #debug "ENTER ref_instance"
+                var mmethod = mmethoddef.mproperty
+                # routine_mclass is the specialized one, e.g: FunRef1, ProcRef2, etc..
+                var routine_mclass = routine_type.mclass
+
+                var nclasses = mmodule.model.get_mclasses_by_name("RoutineRef").as(not null)
+                var base_routine_mclass = nclasses.first
+
+                # All routine classes use the same `NEW` constructor.
+                # However, they have different declared `class` and `type` value.
+                self.require_declaration("NEW_{base_routine_mclass.c_name}")
+
+                var recv_class_cname = recv.mcasttype.as(MClassType).mclass.c_name
+                var my_recv = recv
+
+                if recv.mtype.is_c_primitive then
+                        my_recv = autobox(recv, mmodule.object_type)
+                end
+                var my_recv_mclass_type = my_recv.mtype.as(MClassType)
+
+                # The class of the concrete Routine must exist (e.g ProcRef0, FunRef0, etc.)
+                self.require_declaration("class_{routine_mclass.c_name}")
+                self.require_declaration("type_{routine_type.c_name}")
+
+                compiler.undead_types.add(routine_type)
+                self.require_declaration(mmethoddef.c_name)
+
+                var thunk_function = mmethoddef.callref_thunk(my_recv_mclass_type)
+                # If the receiver is exact, then there's no need to make a
+                # polymorph call to the underlying method.
+                thunk_function.polymorph_call_flag = not my_recv.is_exact
+                var runtime_function = mmethoddef.virtual_runtime_function
+
+                var is_c_equiv = runtime_function.msignature.c_equiv(thunk_function.msignature)
+
+                var c_ref = thunk_function.c_ref
+                if is_c_equiv then
+                        var const_color = mmethoddef.mproperty.const_color
+                        c_ref = "{class_info(my_recv)}->vft[{const_color}]"
+                        self.require_declaration(const_color)
+                else
+                        self.require_declaration(thunk_function.c_name)
+                        compiler.thunk_todo(thunk_function)
+                end
+
+                # Each RoutineRef points to a receiver AND a callref_thunk
+                var res = self.new_expr("NEW_{base_routine_mclass.c_name}({my_recv}, (nitmethod_t){c_ref}, &class_{routine_mclass.c_name}, &type_{routine_type.c_name})", routine_type)
+                #debug "LEAVING ref_instance"
+                return res
+        end
+
+        redef fun routine_ref_call(mmethoddef, arguments)
+        do
+                #debug "ENTER ref_call"
+                compiler.modelbuilder.nb_invok_by_tables += 1
+                if compiler.modelbuilder.toolcontext.opt_invocation_metrics.value then add("count_invoke_by_tables++;")
+                var nclasses = mmodule.model.get_mclasses_by_name("RoutineRef").as(not null)
+                var nclass = nclasses.first
+                var runtime_function = mmethoddef.virtual_runtime_function
+
+                # Save the current receiver since adapt_signature will autobox
+                # the routine receiver which is not the  underlying receiver.
+                # The underlying receiver has already been adapted in the
+                # `routine_ref_instance` method. Here we just want to adapt the
+                # rest of the signature, but it's easier to pass the wrong
+                # receiver in adapt_signature then discards it with `shift`.
+                #
+                # ~~~~nitish
+                # class A; def toto do print "toto"; end
+                # var a = new A
+                # var f = &a.toto <- `a` is the underlying receiver
+                # f.call <- here `f` is the routine receiver
+                # ~~~~
+                var routine = arguments.first
+
+                # Retrieve the concrete routine type
+                var original_recv_c = "(((struct instance_{nclass.c_name}*){arguments[0]})->recv)"
+                var nitmethod = "(({runtime_function.c_funptrtype})(((struct instance_{nclass.c_name}*){arguments[0]})->method))"
+                if arguments.length > 1 then
+                        adapt_signature(mmethoddef, arguments)
+                end
+
+                var ret_mtype = runtime_function.called_signature.return_mtype
+
+                if ret_mtype != null then
+                        # `ret` is actually always nullable Object. When invoking
+                        # a callref, we don't have the original callsite information.
+                        # Thus, we need to recompute the return type of the callsite.
+                        ret_mtype = resolve_for(ret_mtype, routine)
+                end
+
+                # remove the routine's receiver
+                arguments.shift
+                var ss = arguments.join(", ")
+                # replace the receiver with the original one
+                if arguments.length > 0 then
+                        ss = "{original_recv_c}, {ss}"
+                else
+                        ss = original_recv_c
+                end
+
+                arguments.unshift routine # put back the routine ref receiver
+                add "/* {mmethoddef.mproperty} on {arguments.first.inspect}*/"
+                var callsite = "{nitmethod}({ss})"
+                if ret_mtype != null then
+                        var subres = new_expr("{callsite}", ret_mtype)
+                        ret(subres)
+                else
+                        add("{callsite};")
+               end
+        end
+
        fun link_unresolved_type(mclassdef: MClassDef, mtype: MType) do
                assert mtype.need_anchor
                var compiler = self.compiler
@@ -2172,6 +2325,45 @@ redef class MMethodDef
                end
                return res
        end
+
+        # Returns true if the current method definition differ from
+        # its original introduction in terms of receiver type.
+        fun recv_differ_from_intro: Bool
+        do
+                var intromclassdef = mproperty.intro.mclassdef
+               var introrecv = intromclassdef.bound_mtype
+                return self.mclassdef.bound_mtype != introrecv
+        end
+
+        # The C thunk function associated to a mmethoddef. Receives only nullable
+        # Object and cast them to the original mmethoddef signature.
+        fun callref_thunk(recv_mtype: MClassType): SeparateThunkFunction
+        do
+                var res = callref_thunk_cache
+                if res == null then
+                        #var runtime_function = virtual_runtime_function
+                        var object_type = mclassdef.mmodule.object_type
+                        var nullable_object = object_type.as_nullable
+                        var msignature2 = msignature.change_all_mtype_for(nullable_object)
+                        var intromclassdef = mproperty.intro.mclassdef
+
+                        #var introrecv = intromclassdef.bound_mtype
+                        ## If the thunk signature is equivalent to its
+                        ## virtual counterpart, then nothing to do.
+                        #print "recv vs intro : {recv_mtype} vs {introrecv}"
+                        #if msignature2.c_equiv(runtime_function.called_signature) and recv_mtype == introrecv then
+                        #        callref_thunk_cache = res
+                        #        return runtime_function
+                        #end
+                        # receiver cannot be null
+                        res = new SeparateThunkFunction(self, recv_mtype, msignature2, "THUNK_{c_name}", mclassdef.bound_mtype)
+                        res.polymorph_call_flag = true
+                        callref_thunk_cache = res
+                end
+                return res
+        end
+
+        private var callref_thunk_cache: nullable SeparateThunkFunction
        private var separate_runtime_function_cache: nullable SeparateRuntimeFunction
 
        # The C function associated to a mmethoddef, that can be stored into a VFT of a class
@@ -2295,7 +2487,6 @@ class SeparateRuntimeFunction
                 end
         end
 
-
         redef fun end_compile_to_c(v)
         do
                 var compiler = v.compiler
index 01042e4..2bb3a86 100644 (file)
@@ -14,7 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
 # Rapid type analysis on the AST
 #
 # Rapid type analysis is an analyse that aproximates the set of live classes
@@ -701,6 +700,15 @@ redef class ASendExpr
        end
 end
 
+redef class ACallrefExpr
+        redef fun accept_rapid_type_visitor(v)
+        do
+                super
+                var b = v.analysis.live_types.has(callsite.recv)
+                v.add_type(callsite.recv.as(MClassType))
+                v.add_type(mtype.as(MClassType))
+        end
+end
 
 redef class ASendReassignFormExpr
        redef fun accept_rapid_type_visitor(v)
diff --git a/src/test_callref.nit b/src/test_callref.nit
new file mode 100644 (file)
index 0000000..59e2b47
--- /dev/null
@@ -0,0 +1,104 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2019-2020 Louis-Vincent Boudreault <lv.boudreault95@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import functional
+
+redef class Object
+        fun toto(x: Int): Int
+        do
+                return x + 1
+        end
+end
+
+redef class Int
+        redef fun toto(x) do return x + self
+
+        fun mult_by(x: Int): Int do return x * self
+end
+
+class A
+        fun fun1: String
+        do
+                return "in A::fun1"
+        end
+end
+
+class B
+        super A
+
+        redef fun fun1
+        do
+                return "in B::fun1"
+        end
+end
+
+class Counter
+        var x = 0
+        fun incr do x += 1
+end
+
+class C[E]
+        var x: E
+        redef fun to_s
+        do
+                if x != null then
+                        return "x is {x.as(not null)}"
+                end
+                return "x is null"
+        end
+end
+
+var a = new A
+var b: A = new B
+var f1 = &a.fun1
+assert f1.call == "in A::fun1"
+var f2 = &b.fun1
+assert f2.call == "in B::fun1"
+
+var f3 = &10.mult_by
+assert f3.call(10) == 100
+
+var f4 = &f2.call
+assert f4.call == "in B::fun1"
+
+var f5: Fun0[Object] = &f4.call
+assert f5.call == "in B::fun1"
+assert f5.call == "in B::fun1"
+
+assert (&10.toto).call(100) == 110
+assert (&"allo".toto).call(1) == 2
+
+var cnt = new Counter
+var p1 = &cnt.incr
+var ps = [p1,p1,p1,p1,p1]
+
+for p in ps do p.call
+assert cnt.x == 5
+
+var c1 = new C[nullable Object](null)
+var c2 = new C[nullable Int](null)
+
+var f6 = &c1.to_s
+var f7 = &c2.to_s
+
+assert f6.call == "x is null"
+assert f7.call == "x is null"
+
+c1.x = "test"
+c2.x = 100
+
+assert f6.call == "x is test"
+assert f7.call == "x is 100"