From 96e0194ab0699dad5029af12193794c4f78b6fd6 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Wed, 31 Jul 2019 03:51:43 +0200 Subject: [PATCH 1/3] use our own struct for the local method table instead of TypeMapEntry --- src/JuliaInterpreter.jl | 2 +- src/localmethtable.jl | 55 +++++++++++++++++------------------------ src/optimize.jl | 2 +- src/types.jl | 14 ++++++++--- 4 files changed, 35 insertions(+), 38 deletions(-) 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/localmethtable.jl b/src/localmethtable.jl index b55197b9..b60cbde6 100644 --- a/src/localmethtable.jl +++ b/src/localmethtable.jl @@ -11,17 +11,17 @@ 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 + 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::FrameInstance + matches = !is_generated(scopeof(fi.framecode)) || enter_generated == fi.enter_generated if matches for i = 1:nargs if !isa(fargs[i], sig[i]) @@ -34,18 +34,18 @@ 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 - return mi.framecode, mi.sparam_vals + return fi.framecode, fi.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 @@ -55,33 +55,24 @@ function get_call_framecode(fargs::Vector{Any}, parentframe::FrameCode, idx::Int 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 + fi = FrameInstance(framecode, env, is_generated(scopeof(framecode)) && enter_generated) + d_meth = DispatchableMethod(nothing, fi, argtypes) 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] = tme + parentframe.methodtables[idx] = d_meth 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..59bc785d 100644 --- a/src/types.jl +++ b/src/types.jl @@ -53,6 +53,12 @@ function breakpointchar(bps::BreakpointState) return bps.condition === falsecondition ? ' ' : 'd' # no breakpoint : disabled end +mutable struct DispatchableMethod + next::Union{Nothing,DispatchableMethod} # linked-list representation + frameinstance::Any # really a 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 +74,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 +86,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) @@ -331,7 +337,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 +375,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) From 4a704d2932b80edf7e3fd4fb389567d1713ac10e Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Wed, 31 Jul 2019 12:51:51 +0200 Subject: [PATCH 2/3] also cache calls that end up getting compiled in local method table --- src/construct.jl | 6 +++--- src/localmethtable.jl | 36 +++++++++++++++++++++++++++--------- src/types.jl | 5 +++-- 3 files changed, 33 insertions(+), 14 deletions(-) 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 b60cbde6..a5e32fcd 100644 --- a/src/localmethtable.jl +++ b/src/localmethtable.jl @@ -11,6 +11,7 @@ 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 + # 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 @@ -20,8 +21,12 @@ function get_call_framecode(fargs::Vector{Any}, parentframe::FrameCode, idx::Int sig = d_meth.sig.parameters::SimpleVector if length(sig) == nargs # If this is generated, match only if `enter_generated` also matches - fi = d_meth.frameinstance::FrameInstance - matches = !is_generated(scopeof(fi.framecode)) || enter_generated == fi.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]) @@ -38,7 +43,11 @@ function get_call_framecode(fargs::Vector{Any}, parentframe::FrameCode, idx::Int d_methprev.next = d_meth.next d_meth.next = d_meth1 end - return fi.framecode, fi.sparam_vals + if fi isa Compiled + return Compiled(), nothing + else + return fi.framecode, fi.sparam_vals + end end end depth += 1 @@ -52,11 +61,16 @@ function get_call_framecode(fargs::Vector{Any}, parentframe::FrameCode, idx::Int 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 - fi = FrameInstance(framecode, env, is_generated(scopeof(framecode)) && enter_generated) - d_meth = DispatchableMethod(nothing, fi, argtypes) + 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) d_meth.next = parentframe.methodtables[idx] # Drop the oldest d_meth, if necessary @@ -74,5 +88,9 @@ function get_call_framecode(fargs::Vector{Any}, parentframe::FrameCode, idx::Int d_meth.next = nothing end parentframe.methodtables[idx] = d_meth - return framecode, env + if is_compiled + return Compiled(), nothing + else + return framecode, env + end end diff --git a/src/types.jl b/src/types.jl index 59bc785d..bddbf66d 100644 --- a/src/types.jl +++ b/src/types.jl @@ -53,9 +53,10 @@ 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::Any # really a FrameInstance but we have a cyclic dependency + 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 @@ -124,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 From e6e64f259e3a3fd2fba2860e73070c86270559f8 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Wed, 31 Jul 2019 19:14:40 +0200 Subject: [PATCH 3/3] add a typeassert --- src/localmethtable.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/localmethtable.jl b/src/localmethtable.jl index a5e32fcd..9014f29e 100644 --- a/src/localmethtable.jl +++ b/src/localmethtable.jl @@ -46,6 +46,7 @@ function get_call_framecode(fargs::Vector{Any}, parentframe::FrameCode, idx::Int if fi isa Compiled return Compiled(), nothing else + fi = fi::FrameInstance return fi.framecode, fi.sparam_vals end end