compiler: Refactor `AbstractRuntimeFunction`
authorLouis-Vincent Boudreault <lv.boudreault95@gmail.com>
Thu, 15 Aug 2019 19:22:24 +0000 (15:22 -0400)
committerLouis-Vincent Boudreault <lv.boudreault95@gmail.com>
Thu, 15 Aug 2019 19:22:24 +0000 (15:22 -0400)
Removed duplicate code across all the compiler and made
`AbstractRuntimeFunction` implement Template Design pattern.
This allow for better code reuse and easier to customization.

Removed the notion of "virtual function" and unified it with the concept
of thunk function. A thunk is an intermediary function between a caller
and a callee whose purpose is to compute thing before or after the
callee gets invoke. Currently, the only usages of thunks is to do conversion (casting)
before calling the actual callee. Thunks can be created by inheriting
`abstract_compiler::ThunkFunction` which provides a default
implementation. Furthermore, this class simplify the old code of
`SeparateRuntimeFunction` by replacing `if` with actual polymorphism.

Finally, thunks will be used to implement callref mechanics.

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

src/compiler/abstract_compiler.nit
src/compiler/global_compiler.nit
src/compiler/separate_compiler.nit

index 3fc2f40..a6fdb2c 100644 (file)
@@ -2056,7 +2056,26 @@ abstract class AbstractCompilerVisitor
 end
 
 # A C function associated to a Nit method
-# Because of customization, a given Nit method can be compiler more that once
+# This is the base class for all runtime representation of a nit method.
+# It implements the Template Design Pattern whose steps are :
+#       1. create the receiver `RuntimeVariable` (selfvar)
+#       2. create a `StaticFrame`
+#       3. resolve the return type.
+#       4. write the function signature
+#       5. Declare the function signature (for C header files)
+#       6. writer the function body
+#       7. post-compiler hook (optional)
+# Each step is called in `AbstractRuntimeFunction::compile_to_c`. A default
+# body is provided foreach step except for compilation hooks. Subclasses may
+# redefine any of the above mentioned steps. However, it's not recommanded
+# to override `compile_to_c` since there's an ordering of the compilation steps.
+# Any information between steps must be saved in the visitor. Currently most
+# of the future runtime info are stored in the `StaticFrame` of the visitor,
+# e.g. the receiver (selfvar), the arguments, etc.
+#
+# Moreover, any subclass may redefine : the receiver type, the return type and
+# the signature. This allow for a better customization for the final implementation.
+# Because of customization, a given Nit method can be compile more that once.
 abstract class AbstractRuntimeFunction
 
        type COMPILER: AbstractCompiler
@@ -2065,6 +2084,8 @@ abstract class AbstractRuntimeFunction
        # The associated Nit method
        var mmethoddef: MMethodDef
 
+        protected var return_mtype: nullable MType = null
+
        # The mangled c name of the runtime_function
        # Subclasses should redefine `build_c_name` instead
        fun c_name: String
@@ -2076,6 +2097,8 @@ abstract class AbstractRuntimeFunction
                return res
        end
 
+        fun c_ref: String do return "&{c_name}"
+
        # Non cached version of `c_name`
        protected fun build_c_name: String is abstract
 
@@ -2085,11 +2108,248 @@ abstract class AbstractRuntimeFunction
        # May inline the body or generate a C function call
        fun call(v: VISITOR, arguments: Array[RuntimeVariable]): nullable RuntimeVariable is abstract
 
-       # Generate the code for the `AbstractRuntimeFunction`
-       # Warning: compile more than once compilation makes CC unhappy
-       fun compile_to_c(compiler: COMPILER) is abstract
+        # Returns `true` if the associated `mmethoddef`'s return type isn't null,
+        # otherwise `false`.
+        fun has_return: Bool
+        do
+                return mmethoddef.msignature.return_mtype != null
+        end
+
+        # The current msignature to use when compiling : `signature_to_c` and `body_to_c`.
+        # This method is useful since most concrete implementation doesn't use the
+        # mmethoddef's signature. By providing a definition in the abstract class,
+        # subclasses can use any msignature.
+        fun msignature: MSignature
+        do
+                return mmethoddef.msignature.as(not null)
+        end
+
+        # The current receiver type to compile : `signature_to_c` and `body_to_c`.
+        # See `msignature` method for more information.
+        protected fun recv_mtype: MType
+        do
+                return mmethoddef.mclassdef.bound_mtype
+        end
+
+        # Prepare the `self` runtime variable to be used by the rest of
+        # compilation steps.
+        # Step 1
+        protected fun resolve_receiver(v: VISITOR): RuntimeVariable
+        do
+                var casttype = mmethoddef.mclassdef.bound_mtype
+                return new RuntimeVariable("self", recv_mtype, casttype)
+        end
+
+        # Builds the static frame for current runtime method
+        # Step 2
+        protected fun build_frame(v: VISITOR, arguments: Array[RuntimeVariable]): StaticFrame
+        do
+                return new StaticFrame(v, mmethoddef, recv_mtype.as(MClassType), arguments)
+        end
+
+        # Step 3 : Returns the return type used by the runtime function.
+        protected fun resolve_return_mtype(v: VISITOR)
+        do
+                return_mtype = msignature.return_mtype
+        end
+
+        # Fills the argument array inside v.frame.arguments, calling `resolve_ith_parameter`
+        # for each parameter.
+        private fun fill_parameters(v: VISITOR)
+        do
+                assert v.frame != null
+                for i in [0..msignature.arity[ do
+                        var arg = resolve_ith_parameter(v, i)
+                       v.frame.arguments.add(arg)
+               end
+        end
+
+        # Step 4 : Creates `RuntimeVariable` for each method argument.
+        protected fun resolve_ith_parameter(v: VISITOR, i: Int): RuntimeVariable
+        do
+                var mp = msignature.mparameters[i]
+                var mtype = mp.mtype
+                if mp.is_vararg then
+                       mtype = v.mmodule.array_type(mtype)
+               end
+                return new RuntimeVariable("p{i}", mtype, mtype)
+        end
+
+        # Generate the code for the signature with an open curly brace
+        #
+        # Returns the generated signature without a semicolon and curly brace,
+        # e.g `RES f(T0 p0, T1 p1, ..., TN pN)`
+        # Step 5
+        protected fun signature_to_c(v: VISITOR): String
+        do
+                assert v.frame != null
+                var arguments = v.frame.arguments
+                var comment = new FlatBuffer
+                var selfvar = v.frame.selfvar
+                var c_ret = "void"
+                if has_return then
+                        c_ret = "{return_mtype.ctype}"
+                end
+                var sig = new FlatBuffer
+                sig.append("{c_ret} {c_name}({recv_mtype.ctype} self")
+               comment.append("({selfvar}: {selfvar.mtype}")
+
+                for i in [0..arguments.length-1[ do
+                        # Skip the receiver
+                        var arg = arguments[i+1]
+                       comment.append(", {arg.mtype}")
+                        sig.append(", {arg.mtype.ctype} p{i}")
+               end
+                sig.append(")")
+               comment.append(")")
+                if has_return then
+                        comment.append(": {return_mtype.as(not null)}")
+                end
+                v.add_decl("/* method {self} for {comment} */")
+                v.add_decl("{sig} \{")
+                return sig.to_s
+        end
+
+        # How the concrete compiler will declare the method, e.g inside a global header file,
+        # extern signature, etc.
+        # Step 6
+        protected fun declare_signature(v: VISITOR, signature: String) is abstract
+
+
+        # Generate the code for the body without return statement at the end and
+        # no curly brace.
+        # Step 7
+        protected fun body_to_c(v: VISITOR)
+        do
+                mmethoddef.compile_inside_to_c(v, v.frame.arguments)
+        end
+
+        # Hook called at the end of `compile_to_c` function. This function
+        # is useful if you need to register any function compiled to c.
+        # Step 8 (optional).
+        protected fun end_compile_to_c(v: VISITOR)
+        do
+                # Nothing to do by default
+        end
+
+       # Generate the code
+        fun compile_to_c(compiler: COMPILER)
+        do
+                var v = compiler.new_visitor
+                var selfvar = resolve_receiver(v)
+                var arguments = [selfvar]
+                var frame = build_frame(v, arguments)
+                v.frame = frame
+
+                resolve_return_mtype(v)
+                fill_parameters(v)
+
+                # WARNING: the signature must be declared before creating
+                # any returnlabel and returnvar (`StaticFrame`). Otherwise,
+                # you could end up with variable outside the function.
+                var sig = signature_to_c(v)
+                declare_signature(v, sig)
+
+                frame.returnlabel = v.get_name("RET_LABEL")
+                if has_return then
+                        var ret_mtype = return_mtype
+                        assert ret_mtype != null
+                        frame.returnvar = v.new_var(ret_mtype)
+                end
+
+                body_to_c(v)
+                v.add("{frame.returnlabel.as(not null)}:;")
+               if has_return then
+                       v.add("return {frame.returnvar.as(not null)};")
+               end
+                v.add "\}"
+                end_compile_to_c(v)
+        end
 end
 
+# Base class for all thunk-like function. A thunk is a function whose purpose
+# is to call another function. Like a class wrapper or decorator, a thunk is used
+# to computer things (conversions, log, etc) before or after a function call. It's
+# an intermediary between the caller and the callee.
+#
+# The most basic use of a thunk is to unbox its argument before invoking the real callee.
+# Virtual functions are a use case of thunk function:
+#
+# ~~~~nitish
+# redef class Object
+#       fun toto(x: Int): Int do return 1 + x
+# end
+# redef class Int
+#       redef fun toto(x) do return x + self
+# end
+#
+# ```generated C code
+# long Object__toto(val* self, long x) { return 1 + x }
+# long Int__toto(long self, long x) { return x + self }
+# long THUNK_Int__toto(val* self, long x) {
+#       long self2 = (long)(self)>>2    // Unboxing from Object to Int
+#       return Int_toto(self2, x)
+# }
+#
+# ```
+# ~~~~
+#
+# A thunk has its OWN SIGNATURE and is considered like any other `AbstractRuntimeFunction`.
+# Thus, one might need to be careful when overriding parent's method. Since most usage of
+# thunks in Nit is to unbox and box things between a caller and a callee, the default
+# implementation is doing just that. In other words, a thunk takes a signature and a method
+# and tries to cast its signature to the underlying method's signature.
+#
+# A virtual function has the same signature as its `mmethoddef` field, except for
+# its receiver is of type `Object`.
+# In the same vibe, a call reference has all of its argument boxed as `Object`.
+abstract class ThunkFunction
+        super AbstractRuntimeFunction
+
+        # Determines if the callsite should be polymorphic or static.
+        var polymorph_call_flag = false is writable
+
+        # The type expected by the callee. Used to resolve `mmethoddef`'s formal
+        # parameters and virtual type. This type must NOT need anchor.
+        fun target_recv: MType is abstract
+
+        redef fun body_to_c(v)
+        do
+                assert not target_recv.need_anchor
+                var frame = v.frame
+                assert frame != null
+                var selfvar = frame.selfvar
+                var arguments = frame.arguments
+                var arguments2 = new Array[RuntimeVariable]
+                arguments2.push(v.autobox(selfvar, target_recv))
+                var resolved_sig = msignature.resolve_for(target_recv, target_recv.as(MClassType), v.mmodule, true)
+                for i in [0..resolved_sig.arity[ do
+                var param = resolved_sig.mparameters[i]
+                        var mtype = param.mtype
+                        if param.is_vararg then
+                               mtype = v.mmodule.array_type(mtype)
+                       end
+                        var temp = v.autobox(arguments[i+1], mtype)
+                        arguments2.push(temp)
+                end
+                v.add("/* {mmethoddef}, {recv_mtype.ctype} */")
+                var subret: nullable RuntimeVariable = null
+                if polymorph_call_flag then
+                        subret = v.send(mmethoddef.mproperty, arguments2)
+                else
+                        subret = v.call(mmethoddef, arguments2[0].mcasttype.as(MClassType), arguments2)
+                end
+                if has_return then
+                        assert subret != null
+                        var subret2 = v.autobox(subret, return_mtype.as(not null))
+                        v.assign(frame.returnvar.as(not null), subret2)
+                end
+
+        end
+
+end
+
+
 # A runtime variable hold a runtime value in C.
 # Runtime variables are associated to Nit local variables and intermediate results in Nit expressions.
 #
@@ -2164,6 +2424,14 @@ class StaticFrame
 
        # The array comprehension currently filled, if any
        private var comprehension: nullable RuntimeVariable = null
+
+        # Returns the first argument (the receiver) of a frame.
+        # REQUIRE: arguments.length >= 1
+        fun selfvar: RuntimeVariable
+        do
+                assert arguments.length >= 1
+                return arguments.first
+        end
 end
 
 redef class MType
index 718cb5d..ce61657 100644 (file)
@@ -926,7 +926,7 @@ class GlobalCompilerVisitor
        end
 end
 
-# A runtime function customized on a specific monomrph receiver type
+# A runtime function customized on a specific monomorph receiver type
 private class CustomizedRuntimeFunction
        super AbstractRuntimeFunction
 
@@ -971,74 +971,51 @@ private class CustomizedRuntimeFunction
                end
        end
 
-       # compile the code customized for the reciever
-       redef fun compile_to_c(compiler)
-       do
-               var recv = self.recv
-               var mmethoddef = self.mmethoddef
-               if not recv.is_subtype(compiler.mainmodule, null, mmethoddef.mclassdef.bound_mtype) then
-                       print("problem: why do we compile {self} for {recv}?")
-                       abort
-               end
-
-               var v = compiler.new_visitor
-               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 StaticFrame(v, mmethoddef, recv, arguments)
-               v.frame = frame
-
-               var sig = new FlatBuffer
-               var comment = new FlatBuffer
-               var ret = mmethoddef.msignature.return_mtype
-               if ret != null then
-                       ret = v.resolve_for(ret, selfvar)
-                       sig.append("{ret.ctype} ")
-               else
-                       sig.append("void ")
-               end
-               sig.append(self.c_name)
-               sig.append("({recv.ctype} {selfvar}")
-               comment.append("(self: {recv}")
-               arguments.add(selfvar)
-               for i in [0..mmethoddef.msignature.arity[ do
-                       var mp = mmethoddef.msignature.mparameters[i]
-                       var mtype = mp.mtype
-                       if mp.is_vararg then
-                               mtype = v.mmodule.array_type(mtype)
-                       end
-                       mtype = v.resolve_for(mtype, selfvar)
-                       comment.append(", {mtype}")
-                       sig.append(", {mtype.ctype} p{i}")
-                       var argvar = new RuntimeVariable("p{i}", mtype, mtype)
-                       arguments.add(argvar)
-               end
-               sig.append(")")
-               comment.append(")")
-               if ret != null then
-                       comment.append(": {ret}")
-               end
-               compiler.header.add_decl("{sig};")
+        redef fun recv_mtype
+        do
+                return recv
+        end
 
-               v.add_decl("/* method {self} for {comment} */")
-               v.add_decl("{sig} \{")
-               #v.add("printf(\"method {self} for {comment}\\n\");")
-               if ret != null then
-                       frame.returnvar = v.new_var(ret)
-               end
-               frame.returnlabel = v.get_name("RET_LABEL")
+        redef var return_mtype
 
-               mmethoddef.compile_inside_to_c(v, arguments)
-
-               v.add("{frame.returnlabel.as(not null)}:;")
-               if ret != null then
-                       v.add("return {frame.returnvar.as(not null)};")
+        redef fun resolve_receiver(v)
+        do
+                var selfvar = new RuntimeVariable("self", recv, recv)
+               if v.compiler.runtime_type_analysis.live_types.has(recv) then
+                       selfvar.is_exact = true
                end
-               v.add("\}")
-               if not self.c_name.has_substring("VIRTUAL", 0) then compiler.names[self.c_name] = "{mmethoddef.mclassdef.mmodule.name}::{mmethoddef.mclassdef.mclass.name}::{mmethoddef.mproperty.name} ({mmethoddef.location.file.filename}:{mmethoddef.location.line_start})"
-       end
+                return selfvar
+        end
+
+        redef fun resolve_return_mtype(v)
+        do
+                var selfvar = v.frame.selfvar
+                if has_return then
+                        var ret = msignature.return_mtype.as(not null)
+                        return_mtype = v.resolve_for(ret, selfvar)
+                end
+        end
+        redef fun resolve_ith_parameter(v, i)
+        do
+                var selfvar = v.frame.selfvar
+                var mp = msignature.mparameters[i]
+                var mtype = mp.mtype
+                if mp.is_vararg then
+                        mtype = v.mmodule.array_type(mtype)
+                end
+                mtype = v.resolve_for(mtype, selfvar)
+                return new RuntimeVariable("p{i}", mtype, mtype)
+        end
+
+        redef fun declare_signature(v, sig)
+        do
+                v.compiler.header.add_decl("{sig};")
+        end
+
+        redef fun end_compile_to_c(v)
+        do
+               if not self.c_name.has_substring("VIRTUAL", 0) then v.compiler.names[self.c_name] = "{mmethoddef.mclassdef.mmodule.name}::{mmethoddef.mclassdef.mclass.name}::{mmethoddef.mproperty.name} ({mmethoddef.location.file.filename}:{mmethoddef.location.line_start})"
+        end
 
        redef fun call(v: VISITOR, arguments: Array[RuntimeVariable]): nullable RuntimeVariable
        do
index 15b6f05..f9d1444 100644 (file)
@@ -2197,10 +2197,7 @@ redef class MMethodDef
                                self.virtual_runtime_function_cache = res
                                return res
                        end
-
-                       res = new SeparateRuntimeFunction(self, recv, msignature, "VIRTUAL_{c_name}")
-                       self.virtual_runtime_function_cache = res
-                       res.is_thunk = true
+                        res = new SeparateThunkFunction(self, recv, msignature, "VIRTUAL_{c_name}", mclassdef.bound_mtype)
                end
                return res
        end
@@ -2237,11 +2234,23 @@ class SeparateRuntimeFunction
        # The name on the compiled method
        redef var build_c_name: String
 
-       # Statically call the original body instead
-       var is_thunk = false
-
        redef fun to_s do return self.mmethoddef.to_s
 
+        redef fun msignature
+        do
+                return called_signature
+        end
+
+        redef fun recv_mtype
+        do
+                return called_recv
+        end
+
+        redef fun return_mtype
+        do
+                return called_signature.return_mtype
+        end
+
        # The C return type (something or `void`)
        var c_ret: String is lazy do
                var ret = called_signature.return_mtype
@@ -2271,69 +2280,33 @@ class SeparateRuntimeFunction
        # The C type for the function pointer.
        var c_funptrtype: String is lazy do return "{c_ret}(*){c_sig}"
 
-       redef fun compile_to_c(compiler)
-       do
-               var mmethoddef = self.mmethoddef
-
-               var sig = "{c_ret} {c_name}{c_sig}"
-               compiler.provide_declaration(self.c_name, "{sig};")
-
-               var rta = compiler.as(SeparateCompiler).runtime_type_analysis
-
-               var recv = self.mmethoddef.mclassdef.bound_mtype
-               var v = compiler.new_visitor
-               var selfvar = new RuntimeVariable("self", called_recv, recv)
-               var arguments = new Array[RuntimeVariable]
-               var frame = new StaticFrame(v, mmethoddef, recv, arguments)
-               v.frame = frame
+        redef fun declare_signature(v, sig)
+        do
+                v.compiler.provide_declaration(c_name, "{sig};")
+        end
 
-               var msignature = called_signature
-               var ret = called_signature.return_mtype
-
-               var comment = new FlatBuffer
-               comment.append("({selfvar}: {selfvar.mtype}")
-               arguments.add(selfvar)
-               for i in [0..msignature.arity[ do
-                       var mp = msignature.mparameters[i]
-                       var mtype = mp.mtype
-                       if mp.is_vararg then
-                               mtype = v.mmodule.array_type(mtype)
-                       end
-                       comment.append(", {mtype}")
-                       var argvar = new RuntimeVariable("p{i}", mtype, mtype)
-                       arguments.add(argvar)
-               end
-               comment.append(")")
-               if ret != null then
-                       comment.append(": {ret}")
-               end
+        redef fun body_to_c(v)
+        do
+                var rta = v.compiler.as(SeparateCompiler).runtime_type_analysis
+                if rta != null and not rta.live_mmodules.has(mmethoddef.mclassdef.mmodule) then
+                       v.add_abort("FATAL: Dead method executed.")
+                else
+                        super
+                end
+        end
 
-               v.add_decl("/* method {self} for {comment} */")
-               v.add_decl("{sig} \{")
-               if ret != null then
-                       frame.returnvar = v.new_var(ret)
-               end
-               frame.returnlabel = v.get_name("RET_LABEL")
 
-               if is_thunk then
-                       var subret = v.call(mmethoddef, recv, arguments)
-                       if ret != null then
-                               assert subret != null
-                               v.assign(frame.returnvar.as(not null), subret)
-                       end
-               else if rta != null and not rta.live_mmodules.has(mmethoddef.mclassdef.mmodule) then
-                       v.add_abort("FATAL: Dead method executed.")
-               else
-                       mmethoddef.compile_inside_to_c(v, arguments)
-               end
+        redef fun end_compile_to_c(v)
+        do
+                var compiler = v.compiler
+                compiler.names[self.c_name] = "{mmethoddef.full_name} ({mmethoddef.location.file.filename}:{mmethoddef.location.line_start})"
+        end
 
-               v.add("{frame.returnlabel.as(not null)}:;")
-               if ret != null then
-                       v.add("return {frame.returnvar.as(not null)};")
-               end
-               v.add("\}")
-               compiler.names[self.c_name] = "{mmethoddef.full_name} ({mmethoddef.location.file.filename}:{mmethoddef.location.line_start})"
-       end
+        redef fun build_frame(v, arguments)
+        do
+                var recv = mmethoddef.mclassdef.bound_mtype
+                return new StaticFrame(v, mmethoddef, recv, arguments)
+        end
 
        # Compile the trampolines used to implement late-binding.
        #
@@ -2382,6 +2355,12 @@ class SeparateRuntimeFunction
        end
 end
 
+class SeparateThunkFunction
+        super ThunkFunction
+        super SeparateRuntimeFunction
+        redef var target_recv
+end
+
 redef class MType
        # Are values of `self` tagged?
        # If false, it means that the type is not primitive, or is boxed.