From aed3043e88bf84f99591d6044895526536f0141e Mon Sep 17 00:00:00 2001 From: Louis-Vincent Boudreault Date: Thu, 12 Sep 2019 11:01:50 -0400 Subject: [PATCH] Callref expression support for Separate Compiler. - 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 --- src/compiler/abstract_compiler.nit | 50 ++++++++- src/compiler/separate_compiler.nit | 201 +++++++++++++++++++++++++++++++++++- src/rapid_type_analysis.nit | 10 +- src/test_callref.nit | 104 +++++++++++++++++++ 4 files changed, 355 insertions(+), 10 deletions(-) create mode 100644 src/test_callref.nit diff --git a/src/compiler/abstract_compiler.nit b/src/compiler/abstract_compiler.nit index b512cdd..669159f 100644 --- a/src/compiler/abstract_compiler.nit +++ b/src/compiler/abstract_compiler.nit @@ -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 diff --git a/src/compiler/separate_compiler.nit b/src/compiler/separate_compiler.nit index f9d1444..f06e61f 100644 --- a/src/compiler/separate_compiler.nit +++ b/src/compiler/separate_compiler.nit @@ -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 diff --git a/src/rapid_type_analysis.nit b/src/rapid_type_analysis.nit index 01042e4..2bb3a86 100644 --- a/src/rapid_type_analysis.nit +++ b/src/rapid_type_analysis.nit @@ -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 index 0000000..59e2b47 --- /dev/null +++ b/src/test_callref.nit @@ -0,0 +1,104 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2019-2020 Louis-Vincent Boudreault +# +# 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" -- 1.7.9.5