From 9f14ff8157452a0df4ca227b55c6e233762dfc82 Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Wed, 21 Aug 2024 10:38:42 -0400 Subject: [PATCH 01/12] niti: cache anchor_to The gain is non negligible before: ``` $ time nit fib.nit 29 514229 real 0m9,830s user 0m9,706s sys 0m0,120s ``` after: ``` $ time ./nit fib.nit 29 514229 real 0m5,311s user 0m5,191s sys 0m0,115s ``` Signed-off-by: Jean Privat --- src/interpreter/naive_interpreter.nit | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/interpreter/naive_interpreter.nit b/src/interpreter/naive_interpreter.nit index 527f16eb9a..e7b039fa1f 100644 --- a/src/interpreter/naive_interpreter.nit +++ b/src/interpreter/naive_interpreter.nit @@ -119,6 +119,19 @@ class NaiveInterpreter return sub.is_subtype(self.mainmodule, current_receiver_class, sup) end + var anchor_to_cache = new HashMap2[MType, MClassType, MType] + + # Cached anchor resolution in the context of the mainmodule + fun anchor_to(mtype: MType, anchor: MClassType): MType + do + if not mtype.need_anchor then return mtype + var res = anchor_to_cache[mtype, anchor] + if res != null then return res + res = mtype.anchor_to(self.mainmodule, anchor) + anchor_to_cache[mtype, anchor] = res + return res + end + # Get a primitive method in the context of the main module fun force_get_primitive_method(name: String, recv: MType): MMethod do @@ -519,7 +532,7 @@ class NaiveInterpreter end if param.is_vararg and args[i].vararg_decl > 0 then var vararg = exprs.sub(j, args[i].vararg_decl) - var elttype = param.mtype.anchor_to(self.mainmodule, recv.mtype.as(MClassType)) + var elttype = anchor_to(param.mtype, recv.mtype.as(MClassType)) var arg = self.array_instance(vararg, elttype) res.add(arg) continue @@ -588,7 +601,7 @@ class NaiveInterpreter # get the parameter type var mtype = mp.mtype var anchor = args.first.mtype.as(MClassType) - var amtype = mtype.anchor_to(self.mainmodule, anchor) + var amtype = anchor_to(mtype, anchor) if not args[i+1].mtype.is_subtype(self.mainmodule, anchor, amtype) then node.fatal(self, "Cast failed. Expected `{mtype}`, got `{args[i+1].mtype}`") end @@ -691,7 +704,7 @@ class NaiveInterpreter # This function determines the correct type according to the receiver of the current propdef (self). fun unanchor_type(mtype: MType): MType do - return mtype.anchor_to(self.mainmodule, current_receiver_class) + return anchor_to(mtype, current_receiver_class) end # Placebo instance used to mark internal error result when `null` already have a meaning. @@ -1589,7 +1602,7 @@ redef class AAttrPropdef var mpropdef = self.mpropdef if mpropdef == null then return var mtype = self.mtype.as(not null) - mtype = mtype.anchor_to(v.mainmodule, recv.mtype.as(MClassType)) + mtype = v.anchor_to(mtype, recv.mtype.as(MClassType)) if mtype isa MNullableType then v.write_attribute(self.mpropdef.mproperty, recv, v.null_instance) end From f3fe23b2c82d35030718d1e11e1b5827108f1d6e Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Thu, 22 Aug 2024 09:33:56 -0400 Subject: [PATCH 02/12] niti: add a clear_caches method Signed-off-by: Jean Privat --- src/interpreter/naive_interpreter.nit | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/interpreter/naive_interpreter.nit b/src/interpreter/naive_interpreter.nit index e7b039fa1f..d8d55fd9e5 100644 --- a/src/interpreter/naive_interpreter.nit +++ b/src/interpreter/naive_interpreter.nit @@ -113,6 +113,11 @@ class NaiveInterpreter end end + fun clear_caches + do + anchor_to_cache.clear + end + # Subtype test in the context of the mainmodule fun is_subtype(sub, sup: MType): Bool do From 74c47e82ec99e3d866e5b59e0d5a2f8a5aeb5f23 Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Thu, 22 Aug 2024 09:36:13 -0400 Subject: [PATCH 03/12] niti: cache lookup_first_definition Signed-off-by: Jean Privat --- src/interpreter/naive_interpreter.nit | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/interpreter/naive_interpreter.nit b/src/interpreter/naive_interpreter.nit index d8d55fd9e5..32c2a4d9c8 100644 --- a/src/interpreter/naive_interpreter.nit +++ b/src/interpreter/naive_interpreter.nit @@ -116,6 +116,7 @@ class NaiveInterpreter fun clear_caches do anchor_to_cache.clear + lookup_first_definition_cache.clear end # Subtype test in the context of the mainmodule @@ -645,10 +646,23 @@ class NaiveInterpreter var mtype = recv.mtype var ret = send_commons(mproperty, args, mtype) if ret != null then return ret - var propdef = mproperty.lookup_first_definition(self.mainmodule, mtype) + var propdef = lookup_first_definition(mtype, mproperty) return self.call(propdef, args) end + private var lookup_first_definition_cache = new HashMap2[MType, MMethod, MMethodDef] + + # Cached version of lookup_first_definition for the main module + fun lookup_first_definition(mtype: MType, mproperty: MMethod): MMethodDef + do + if mproperty.mpropdefs.length == 1 then return mproperty.mpropdefs.first + var res = lookup_first_definition_cache[mtype, mproperty] + if res != null then return res + res = mproperty.lookup_first_definition(self.mainmodule, mtype) + lookup_first_definition_cache[mtype, mproperty] = res + return res + end + # Read the attribute `mproperty` of an instance `recv` and return its value. # If the attribute in not yet initialized, then aborts with an error message. fun read_attribute(mproperty: MAttribute, recv: Instance): Instance From 2f0a0e6ccaee011fe56e37e565ffd9b7e85564ca Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Thu, 22 Aug 2024 09:37:31 -0400 Subject: [PATCH 04/12] niti: fix PrimitiveInstance:to_s for null Signed-off-by: Jean Privat --- src/interpreter/naive_interpreter.nit | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/interpreter/naive_interpreter.nit b/src/interpreter/naive_interpreter.nit index 32c2a4d9c8..b114679d20 100644 --- a/src/interpreter/naive_interpreter.nit +++ b/src/interpreter/naive_interpreter.nit @@ -855,7 +855,12 @@ class PrimitiveInstance[E] return self.val.is_same_instance(o.val) end - redef fun to_s do return "{mtype}#{val.object_id}({val or else "null"})" + redef fun to_s + do + var val = self.val + if val == null then return "{mtype}#null" + return "{mtype}#{val.object_id}({val})" + end redef fun to_i do return val.as(Int) From 9cfada379bb1459b9d44f746ffd3548378abce07 Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Thu, 22 Aug 2024 09:39:26 -0400 Subject: [PATCH 05/12] niti: cache ASendExpr::raw_arguments Signed-off-by: Jean Privat --- src/interpreter/naive_interpreter.nit | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/interpreter/naive_interpreter.nit b/src/interpreter/naive_interpreter.nit index b114679d20..df4b069bf0 100644 --- a/src/interpreter/naive_interpreter.nit +++ b/src/interpreter/naive_interpreter.nit @@ -2313,6 +2313,9 @@ redef class AOnceExpr end redef class ASendExpr + # We assume the node will not change, so we can cache the raw arguments array once and for all + private var raw_arguments_cache: Array[AExpr] = self.raw_arguments is lazy + redef fun expr(v) do var recv = v.expr(self.n_expr) @@ -2323,7 +2326,7 @@ redef class ASendExpr return recv end - var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.raw_arguments) + var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.raw_arguments_cache) if args == null then return null var res = v.callsite(callsite, args) return res From 8fe29bc4762e04e411a27a1c32d7b336f5a5fdd4 Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Thu, 22 Aug 2024 09:42:56 -0400 Subject: [PATCH 06/12] niti: give the array to fill to aexprs_to_instances Signed-off-by: Jean Privat --- src/interpreter/naive_interpreter.nit | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/interpreter/naive_interpreter.nit b/src/interpreter/naive_interpreter.nit index df4b069bf0..9f046762e1 100644 --- a/src/interpreter/naive_interpreter.nit +++ b/src/interpreter/naive_interpreter.nit @@ -487,20 +487,18 @@ class NaiveInterpreter # Store known methods, used to trace methods as they are reached var discover_call_trace: Set[MMethodDef] = new HashSet[MMethodDef] - # Consumes an iterator of expressions and tries to map each element to - # its corresponding Instance. + # Collect `expr` of each AExpr in order and append the instance to the result. # # If any AExprs doesn't resolve to an Instance, then it returns null. - # Otherwise return an array of instances - fun aexprs_to_instances(aexprs: Iterator[AExpr]): nullable Array[Instance] + # Otherwise return the array of instances + fun aexprs_to_instances(aexprs: Collection[AExpr], result: Array[Instance]): nullable Array[Instance] do - var accumulator = new Array[Instance] for aexpr in aexprs do var instance = expr(aexpr) if instance == null then return null - accumulator.push(instance) + result.push(instance) end - return accumulator + return result end # Evaluate `args` as expressions in the call of `mpropdef` on `recv`. @@ -510,22 +508,20 @@ class NaiveInterpreter fun varargize(mpropdef: MMethodDef, map: nullable SignatureMap, recv: Instance, args: SequenceRead[AExpr]): nullable Array[Instance] do var msignature = mpropdef.msignature.as(not null) - var res = new Array[Instance] + var res = new Array[Instance].with_capacity(msignature.arity+1) res.add(recv) if msignature.arity == 0 then return res if map == null then assert args.length == msignature.arity else debug("Expected {msignature.arity} args, got {args.length}") - var rest_args = aexprs_to_instances(args.iterator) - if rest_args == null then return null - res.append(rest_args) + if aexprs_to_instances(args, res) == null then return null return res end # Eval in order of arguments, not parameters - var exprs = aexprs_to_instances(args.iterator) - if exprs == null then return null + var exprs = new Array[Instance].with_capacity(args.length) + if aexprs_to_instances(args, exprs) == null then return null # Fill `res` with the result of the evaluation according to the mapping for i in [0..msignature.arity[ do From 743f33212805f4cbfc15eb3faecf793d1a269756 Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Thu, 22 Aug 2024 09:46:25 -0400 Subject: [PATCH 07/12] typing: add SignatureMap::straight is the map is trivially in order Signed-off-by: Jean Privat --- src/interpreter/naive_interpreter.nit | 2 +- src/semantize/typing.nit | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/interpreter/naive_interpreter.nit b/src/interpreter/naive_interpreter.nit index 9f046762e1..b2b3cbdb53 100644 --- a/src/interpreter/naive_interpreter.nit +++ b/src/interpreter/naive_interpreter.nit @@ -513,7 +513,7 @@ class NaiveInterpreter if msignature.arity == 0 then return res - if map == null then + if map == null or map.straight then assert args.length == msignature.arity else debug("Expected {msignature.arity} args, got {args.length}") if aexprs_to_instances(args, res) == null then return null return res diff --git a/src/semantize/typing.nit b/src/semantize/typing.nit index 1f9925333d..91350f29aa 100644 --- a/src/semantize/typing.nit +++ b/src/semantize/typing.nit @@ -454,6 +454,7 @@ private class TypeVisitor # Associate each parameter to a position in the arguments var map = new SignatureMap + var straight = args.length == msignature.arity # Special case for the isolated last argument # TODO: reify this method characteristics (where? the param, the signature, the method?) @@ -483,6 +484,7 @@ private class TypeVisitor return null end map.map[idx] = i + if idx != i then straight = false e.mtype = self.visit_expr_subtype(e.n_expr, param.mtype) end @@ -509,6 +511,7 @@ private class TypeVisitor end var arg = args[j] map.map[i] = j + if i != j then straight = false j += 1 if i == vararg_rank then @@ -536,6 +539,7 @@ private class TypeVisitor # Third, check varargs if vararg_rank >= 0 then + straight = false var paramtype = msignature.mparameters[vararg_rank].mtype var first = args[vararg_rank] if vararg_decl == 0 then @@ -548,6 +552,7 @@ private class TypeVisitor end end + map.straight = straight return map end @@ -761,6 +766,9 @@ end class SignatureMap # Associate a parameter to an argument var map = new ArrayMap[Int, Int] + + # Is trivially the ith parameter associated to the ith argument? + var straight = true end # A specific method call site with its associated informations. From 59617cb4c950b159a456b41cefea033b195ed6a5 Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Thu, 22 Aug 2024 11:42:57 -0400 Subject: [PATCH 08/12] nitin: call interpreter.clear_caches Signed-off-by: Jean Privat --- contrib/nitin/nitin.nit | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/nitin/nitin.nit b/contrib/nitin/nitin.nit index 6651636ceb..96ad052eac 100644 --- a/contrib/nitin/nitin.nit +++ b/contrib/nitin/nitin.nit @@ -256,6 +256,7 @@ loop # Run the main if the AST contains a main if main_method != null then do + interpreter.clear_caches interpreter.catch_count += 1 interpreter.send(mainprop, [mainobj]) catch From 2513165bcade559643c5ebd11f6e37572463e97b Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Thu, 22 Aug 2024 13:22:53 -0400 Subject: [PATCH 09/12] fixup: typing: missing a SignatureMap::straight invalidation Signed-off-by: Jean Privat --- src/semantize/typing.nit | 1 + 1 file changed, 1 insertion(+) diff --git a/src/semantize/typing.nit b/src/semantize/typing.nit index 91350f29aa..2b78c9f127 100644 --- a/src/semantize/typing.nit +++ b/src/semantize/typing.nit @@ -523,6 +523,7 @@ private class TypeVisitor var paramtype = param.mtype self.visit_expr_subtype(arg, paramtype) else + straight = false check_one_vararg(arg, param) end end From 09c3e9725e1e9bb07e153b3948886b947c8a1e32 Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Thu, 22 Aug 2024 14:41:58 -0400 Subject: [PATCH 10/12] niti: access variables in an array instead of a hashmap Signed-off-by: Jean Privat --- src/interpreter/naive_interpreter.nit | 38 ++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/interpreter/naive_interpreter.nit b/src/interpreter/naive_interpreter.nit index b2b3cbdb53..d9636acb39 100644 --- a/src/interpreter/naive_interpreter.nit +++ b/src/interpreter/naive_interpreter.nit @@ -473,15 +473,15 @@ class NaiveInterpreter # Retrieve the value of the variable in the current frame fun read_variable(v: Variable): Instance do - var f = frames.first.as(InterpreterFrame) - return f.map[v] + var f = frames.first + return f.read_variable(v, self) end # Assign the value of the variable in the current frame fun write_variable(v: Variable, value: Instance) do - var f = frames.first.as(InterpreterFrame) - f.map[v] = value + var f = frames.first + f.write_variable(v, self, value) end # Store known methods, used to trace methods as they are reached @@ -887,14 +887,40 @@ abstract class Frame var arguments: Array[Instance] # Indicate if the expression has an array comprehension form var comprehension: nullable Array[Instance] = null + # Read access of a variable + fun read_variable(variable: Variable, v: NaiveInterpreter): Instance is abstract + # Write access of a variable + fun write_variable(variable: Variable, v: NaiveInterpreter, value: Instance) is abstract end # Implementation of a Frame with a Hashmap to store local variables class InterpreterFrame super Frame - # Mapping between a variable and the current value - var map: Map[Variable, Instance] = new HashMap[Variable, Instance] + # Mapping between a variable index and the current value + var vars = new Array[Instance] + + redef fun read_variable(variable, v) + do + return vars[variable.index] + end + + redef fun write_variable(variable, v, value) + do + var index = variable.index + if index == -1 then + variable.index = vars.length + vars.add(value) + else + while vars.length < index do vars.add(v.null_instance) # use null as place-holder to fill the array of missing variables + vars[index] = value + end + end +end + +redef class Variable + # Position/numbering of the local variable in the frame. + var index: Int = -1 end redef class ANode From ca96c97c88e10c5f2d2151b7e5db49c5b9d3dfe5 Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Thu, 22 Aug 2024 15:47:40 -0400 Subject: [PATCH 11/12] nitin: use the new frame read/write API Signed-off-by: Jean Privat --- contrib/nitin/nitin.nit | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/contrib/nitin/nitin.nit b/contrib/nitin/nitin.nit index 96ad052eac..78d6fad99e 100644 --- a/contrib/nitin/nitin.nit +++ b/contrib/nitin/nitin.nit @@ -145,20 +145,19 @@ redef class AMethPropdef if injected_variables == null then return super # Inject main variables in the frame - assert f isa InterpreterFrame for variable, i in injected_variables do - f.map[variable] = i + f.write_variable(variable, v, i) end var res = super # Update the values of the variables for variable in injected_variables.keys do - injected_variables[variable] = f.map[variable] + injected_variables[variable] = f.read_variable(variable, v) end # Retrieve the values of the new main variables for variable in new_variables.as(not null) do - injected_variables[variable] = f.map[variable] + injected_variables[variable] = f.read_variable(variable, v) end return res From f67dd0f787e812ffe4d69511ecf31cd57c709341 Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Thu, 22 Aug 2024 21:26:21 -0400 Subject: [PATCH 12/12] niti: clear_caches: add collect_attr_propdef_cache.clear Signed-off-by: Jean Privat --- src/interpreter/naive_interpreter.nit | 1 + 1 file changed, 1 insertion(+) diff --git a/src/interpreter/naive_interpreter.nit b/src/interpreter/naive_interpreter.nit index d9636acb39..e5adfe68f6 100644 --- a/src/interpreter/naive_interpreter.nit +++ b/src/interpreter/naive_interpreter.nit @@ -117,6 +117,7 @@ class NaiveInterpreter do anchor_to_cache.clear lookup_first_definition_cache.clear + collect_attr_propdef_cache.clear end # Subtype test in the context of the mainmodule