diff --git a/src/JuliaInterpreter.jl b/src/JuliaInterpreter.jl index 7f83be63..a31e7c52 100644 --- a/src/JuliaInterpreter.jl +++ b/src/JuliaInterpreter.jl @@ -2,7 +2,7 @@ module JuliaInterpreter using Base.Meta import Base: +, -, convert, isless -using Core: CodeInfo, TypeMapEntry, SimpleVector, LineInfoNode, GotoNode, Slot, +using Core: CodeInfo, SimpleVector, LineInfoNode, GotoNode, Slot, GeneratedFunctionStub, MethodInstance, NewvarNode, TypeName using UUIDs diff --git a/src/construct.jl b/src/construct.jl index 9b707866..828e3b58 100644 --- a/src/construct.jl +++ b/src/construct.jl @@ -248,7 +248,7 @@ function prepare_call(@nospecialize(f), allargs; enter_generated = false) # The generator threw an error. Let's generate the same error by calling it. f(allargs[2:end]...) end - isa(ret, Compiled) && return ret + isa(ret, Compiled) && return ret, argtypes # Typical return framecode, lenv = ret if is_generated(method) && enter_generated @@ -543,7 +543,7 @@ See [`enter_call`](@ref) for a similar approach not based on expressions. function enter_call_expr(expr; enter_generated = false) clear_caches() r = determine_method_for_expr(expr; enter_generated = enter_generated) - if isa(r, Tuple) + if r !== nothing && !isa(r[1], Compiled) return prepare_frame(r[1:end-1]...) end nothing @@ -597,7 +597,7 @@ function enter_call(@nospecialize(finfo), @nospecialize(args...); kwargs...) error(f, " is a builtin or intrinsic") end r = prepare_call(f, allargs; enter_generated=enter_generated) - if isa(r, Tuple) + if r !== nothing && !isa(r[1], Compiled) return prepare_frame(r[1:end-1]...) end return nothing diff --git a/src/localmethtable.jl b/src/localmethtable.jl index b55197b9..9014f29e 100644 --- a/src/localmethtable.jl +++ b/src/localmethtable.jl @@ -11,17 +11,22 @@ function get_call_framecode(fargs::Vector{Any}, parentframe::FrameCode, idx::Int nargs = length(fargs) # includes f as the first "argument" # Determine whether we can look up the appropriate framecode in the local method table if isassigned(parentframe.methodtables, idx) # if this is the first call, this may not yet be set - tme = tme1 = parentframe.methodtables[idx]::TypeMapEntry - local tmeprev + # The case where `methodtables[idx]` is a `Compiled` has already been handled in `bypass_builtins` + d_meth = d_meth1 = parentframe.methodtables[idx]::DispatchableMethod + local d_methprev depth = 1 while true # TODO: consider using world age bounds to handle cache invalidation # Determine whether the argument types match the signature - sig = tme.sig.parameters::SimpleVector + sig = d_meth.sig.parameters::SimpleVector if length(sig) == nargs # If this is generated, match only if `enter_generated` also matches - mi = tme.func::FrameInstance - matches = !is_generated(scopeof(mi.framecode)) || enter_generated == mi.enter_generated + fi = d_meth.frameinstance + if fi isa FrameInstance + matches = !is_generated(scopeof(fi.framecode)) || enter_generated == fi.enter_generated + else + matches = !enter_generated + end if matches for i = 1:nargs if !isa(fargs[i], sig[i]) @@ -34,54 +39,59 @@ function get_call_framecode(fargs::Vector{Any}, parentframe::FrameCode, idx::Int # Rearrange the list to place this method first # (if we're in a loop, we'll likely match this one again on the next iteration) if depth > 1 - parentframe.methodtables[idx] = tme - tmeprev.next = tme.next - tme.next = tme1 + parentframe.methodtables[idx] = d_meth + d_methprev.next = d_meth.next + d_meth.next = d_meth1 + end + if fi isa Compiled + return Compiled(), nothing + else + fi = fi::FrameInstance + return fi.framecode, fi.sparam_vals end - return mi.framecode, mi.sparam_vals end end depth += 1 - tmeprev = tme - tme = tme.next - tme === nothing && break - tme = tme::TypeMapEntry + d_methprev = d_meth + d_meth = d_meth.next + d_meth === nothing && break + d_meth = d_meth::DispatchableMethod end end # We haven't yet encountered this argtype combination and need to look it up by dispatch fargs[1] = f = to_function(fargs[1]) ret = prepare_call(f, fargs; enter_generated=enter_generated) ret === nothing && return f(fargs[2:end]...), nothing - isa(ret, Compiled) && return ret, nothing - framecode, args, env, argtypes = ret - # Store the results of the method lookup in the local method table - mi = FrameInstance(framecode, env, is_generated(scopeof(framecode)) & enter_generated) - # it's sort of odd to call this a TypeMapEntry, then set most of the fields incorrectly - # but since we're just using it as a linked list, it's probably ok - tme = ccall(:jl_new_struct_uninit, Any, (Any,), TypeMapEntry)::TypeMapEntry - tme.func = mi - tme.simplesig = nothing - tme.sig = argtypes - tme.isleafsig = true - tme.issimplesig = false - method = framecode.scope::Method - tme.va = method.isva + is_compiled = isa(ret[1], Compiled) + local framecode + if is_compiled + d_meth = DispatchableMethod(nothing, Compiled(), ret[2]) + else + framecode, args, env, argtypes = ret + # Store the results of the method lookup in the local method table + fi = FrameInstance(framecode, env, is_generated(scopeof(framecode)) && enter_generated) + d_meth = DispatchableMethod(nothing, fi, argtypes) + end if isassigned(parentframe.methodtables, idx) - tme.next = parentframe.methodtables[idx] - # Drop the oldest tme, if necessary - tmetmp = tme.next + d_meth.next = parentframe.methodtables[idx] + # Drop the oldest d_meth, if necessary + d_methtmp = d_meth.next depth = 2 - while isdefined(tmetmp, :next) && tmetmp.next !== nothing + while d_methtmp.next !== nothing depth += 1 - tmetmp = tmetmp.next + d_methtmp = d_methtmp.next depth >= max_methods && break end if depth >= max_methods - tmetmp.next = nothing + d_methtmp.next = nothing end else - tme.next = nothing + d_meth.next = nothing + end + parentframe.methodtables[idx] = d_meth + if is_compiled + return Compiled(), nothing + else + return framecode, env end - parentframe.methodtables[idx] = tme - return framecode, env end diff --git a/src/optimize.jl b/src/optimize.jl index fd09f1db..d01066f5 100644 --- a/src/optimize.jl +++ b/src/optimize.jl @@ -232,7 +232,7 @@ function optimize!(code::CodeInfo, scope) code.ssavaluetypes = length(new_code) # Insert the foreigncall wrappers at the updated idxs - methodtables = Vector{Union{Compiled,TypeMapEntry}}(undef, length(code.code)) + methodtables = Vector{Union{Compiled,DispatchableMethod}}(undef, length(code.code)) for idx in foreigncalls_idx methodtables[ssalookup[idx]] = Compiled() end diff --git a/src/types.jl b/src/types.jl index c073f384..bddbf66d 100644 --- a/src/types.jl +++ b/src/types.jl @@ -53,6 +53,13 @@ function breakpointchar(bps::BreakpointState) return bps.condition === falsecondition ? ' ' : 'd' # no breakpoint : disabled end +abstract type AbstractFrameInstance end +mutable struct DispatchableMethod + next::Union{Nothing,DispatchableMethod} # linked-list representation + frameinstance::Union{Compiled, AbstractFrameInstance} # really a Union{Compiled, FrameInstance} but we have a cyclic dependency + sig::Type # for speed of matching, this is a *concrete* signature. `sig <: frameinstance.framecode.scope.sig` +end + """ `FrameCode` holds static information about a method or toplevel code. One `FrameCode` can be shared by many calling `Frame`s. @@ -68,7 +75,7 @@ Important fields: struct FrameCode scope::Union{Method,Module} src::CodeInfo - methodtables::Vector{Union{Compiled,TypeMapEntry}} # line-by-line method tables for generic-function :call Exprs + methodtables::Vector{Union{Compiled,DispatchableMethod}} # line-by-line method tables for generic-function :call Exprs breakpoints::Vector{BreakpointState} used::BitSet generator::Bool # true if this is for the expression-generator of a @generated function @@ -80,7 +87,7 @@ function FrameCode(scope, src::CodeInfo; generator=false, optimize=true) src, methodtables = optimize!(copy_codeinfo(src), scope) else src = replace_coretypes!(copy_codeinfo(src)) - methodtables = Vector{Union{Compiled,TypeMapEntry}}(undef, length(src.code)) + methodtables = Vector{Union{Compiled,DispatchableMethod}}(undef, length(src.code)) end breakpoints = Vector{BreakpointState}(undef, length(src.code)) for (i, pc_expr) in enumerate(src.code) @@ -118,7 +125,7 @@ Fields: - `framecode`: the [`FrameCode`](@ref) for the method. - `sparam_vals`: the static parameter values for the method. """ -struct FrameInstance +struct FrameInstance <: AbstractFrameInstance framecode::FrameCode sparam_vals::SimpleVector enter_generated::Bool @@ -331,7 +338,7 @@ struct BreakpointSignature <: AbstractBreakpoint enabled::Ref{Bool} instances::Vector{BreakpointRef} end -same_location(bp2::BreakpointSignature, bp::BreakpointSignature) = +same_location(bp2::BreakpointSignature, bp::BreakpointSignature) = bp2.f == bp.f && bp2.sig == bp.sig && bp2.line == bp.line function Base.show(io::IO, bp::BreakpointSignature) print(io, bp.f) @@ -369,7 +376,7 @@ struct BreakpointFileLocation <: AbstractBreakpoint enabled::Ref{Bool} instances::Vector{BreakpointRef} end -same_location(bp2::BreakpointFileLocation, bp::BreakpointFileLocation) = +same_location(bp2::BreakpointFileLocation, bp::BreakpointFileLocation) = bp2.path == bp.path && bp2.abspath == bp.abspath && bp2.line == bp.line function Base.show(io::IO, bp::BreakpointFileLocation) print(io, bp.path, ':', bp.line)