Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

niti: cache anchor_to and some other optimizations #2852

Merged
merged 12 commits into from
Aug 23, 2024
8 changes: 4 additions & 4 deletions contrib/nitin/nitin.nit
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -256,6 +255,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
Expand Down
117 changes: 90 additions & 27 deletions src/interpreter/naive_interpreter.nit
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,32 @@ class NaiveInterpreter
end
end

fun clear_caches
do
anchor_to_cache.clear
lookup_first_definition_cache.clear
collect_attr_propdef_cache.clear
end

# Subtype test in the context of the mainmodule
fun is_subtype(sub, sup: MType): Bool
do
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
Expand Down Expand Up @@ -454,34 +474,32 @@ 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
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`.
Expand All @@ -491,22 +509,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
if map == null or map.straight 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
Expand All @@ -519,7 +535,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
Expand Down Expand Up @@ -588,7 +604,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
Expand Down Expand Up @@ -627,10 +643,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
Expand Down Expand Up @@ -691,7 +720,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.
Expand Down Expand Up @@ -823,7 +852,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)

Expand Down Expand Up @@ -854,14 +888,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
Expand Down Expand Up @@ -1589,7 +1649,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
Expand Down Expand Up @@ -2276,6 +2336,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)
Expand All @@ -2286,7 +2349,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
Expand Down
9 changes: 9 additions & 0 deletions src/semantize/typing.nit
Original file line number Diff line number Diff line change
Expand Up @@ -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?)
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand All @@ -520,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
Expand All @@ -536,6 +540,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
Expand All @@ -548,6 +553,7 @@ private class TypeVisitor
end
end

map.straight = straight
return map
end

Expand Down Expand Up @@ -761,6 +767,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.
Expand Down
Loading