Skip to content

Commit 837b899

Browse files
authored
deprecate @lookup macro in favor of lookup function (#685)
The `@lookup frame node` macro effectively inlines code in the caller’s context to choose lookup subroutines based on the type of `node`, but there’s no performance benefit to doing this. A regular function call should deliver equivalent performance, and using a function improves maintainability. Below are the benchmark results: > master (49220f9) ``` julia> run(SUITE["recursive self 1_000"]) BenchmarkTools.Trial: 1254 samples with 1 evaluation per sample. Range (min … max): 3.634 ms … 13.210 ms ┊ GC (min … max): 0.00% … 0.00% Time (median): 3.845 ms ┊ GC (median): 0.00% Time (mean ± σ): 3.979 ms ± 514.632 μs ┊ GC (mean ± σ): 2.15% ± 6.18% ▄▇▆█▆▂▁ ▃▇███████▇▇▇▅▄▅▄▄▄▃▃▂▂▂▂▁▁▁▁▂▂▁▂▂▂▂▂▂▂▂▁▂▂▂▂▂▂▂▃▂▂▃▂▂▂▂▂▁▂▂ ▃ 3.63 ms Histogram: frequency by time 5.54 ms < Memory estimate: 1.33 MiB, allocs estimate: 44348. julia> run(SUITE["tight loop 10_000"]) BenchmarkTools.Trial: 29 samples with 1 evaluation per sample. Range (min … max): 170.913 ms … 188.887 ms ┊ GC (min … max): 0.00% … 0.46% Time (median): 176.319 ms ┊ GC (median): 0.70% Time (mean ± σ): 176.275 ms ± 3.806 ms ┊ GC (mean ± σ): 0.61% ± 0.42% ▁▁▁▁ ▁ █ █▁▁▁ █ █ █▁▁█▁▁ ▁▁ ▁ ▁ ▁ ████▁█▁█▁████▁▁▁█▁█▁▁██████▁██▁█▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ ▁ 171 ms Histogram: frequency by time 189 ms < Memory estimate: 24.09 MiB, allocs estimate: 1386513. julia> run(SUITE["throw long 1_000"]) BenchmarkTools.Trial: 157 samples with 1 evaluation per sample. Range (min … max): 29.388 ms … 151.134 ms ┊ GC (min … max): 0.00% … 13.02% Time (median): 30.651 ms ┊ GC (median): 0.00% Time (mean ± σ): 31.861 ms ± 9.778 ms ┊ GC (mean ± σ): 0.94% ± 2.53% ▂▅▇█ ▁ ▃██████▆█▆▆▃▁▃▁▃▁▃▁▁▃▃▁▁▃▁▃▁▃▁▁▁▁▁▃▃▁▁▁▁▁▁▁▁▁▁▁▁▁▃▁▁▁▁▁▁▁▁▁▃ ▃ 29.4 ms Histogram: frequency by time 42.2 ms < Memory estimate: 3.75 MiB, allocs estimate: 85380. ``` > this PR ``` julia> run(SUITE["recursive self 1_000"]) BenchmarkTools.Trial: 1277 samples with 1 evaluation per sample. Range (min … max): 3.662 ms … 7.569 ms ┊ GC (min … max): 0.00% … 49.27% Time (median): 3.809 ms ┊ GC (median): 0.00% Time (mean ± σ): 3.908 ms ± 341.900 μs ┊ GC (mean ± σ): 1.76% ± 5.62% ▃▅█▄▂▂ ▄▇██████▇▄▄▃▃▃▃▃▃▃▃▃▃▂▂▂▁▂▂▂▂▁▂▁▁▁▁▁▁▁▂▁▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂ ▃ 3.66 ms Histogram: frequency by time 5.27 ms < Memory estimate: 1.33 MiB, allocs estimate: 44348. julia> run(SUITE["tight loop 10_000"]) BenchmarkTools.Trial: 29 samples with 1 evaluation per sample. Range (min … max): 170.863 ms … 178.231 ms ┊ GC (min … max): 0.57% … 2.61% Time (median): 173.559 ms ┊ GC (median): 0.56% Time (mean ± σ): 173.568 ms ± 1.643 ms ┊ GC (mean ± σ): 0.55% ± 0.57% ▁█ ▁ █ ▁ ▁▁ ▁▁▁█▁▁▁█ ██▁ ▁ ▁ ▁ ▁ ▁ ██▁▁▁█▁▁▁█▁█▁██▁▁▁████████▁███▁▁▁█▁▁▁█▁▁█▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ ▁ 171 ms Histogram: frequency by time 178 ms < Memory estimate: 24.09 MiB, allocs estimate: 1386514. julia> run(SUITE["throw long 1_000"]) BenchmarkTools.Trial: 175 samples with 1 evaluation per sample. Range (min … max): 27.760 ms … 37.426 ms ┊ GC (min … max): 0.00% … 20.80% Time (median): 28.402 ms ┊ GC (median): 0.00% Time (mean ± σ): 28.698 ms ± 1.185 ms ┊ GC (mean ± σ): 0.65% ± 2.76% ▄█▄▁ ▄ ▃▅█████▇▇█▄▄▂▃▂▂▃▁▂▁▁▁▁▁▁▁▁▂▁▁▁▁▁▁▁▁▂▁▁▁▁▁▁▂▂▁▁▁▁▁▂▁▁▁▁▁▁▁▂ ▂ 27.8 ms Histogram: frequency by time 34.3 ms < Memory estimate: 3.75 MiB, allocs estimate: 85380. ```
1 parent 49220f9 commit 837b899

File tree

7 files changed

+159
-140
lines changed

7 files changed

+159
-140
lines changed

bin/generate_builtins.jl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ function generate_fcall_nargs(fname, minarg, maxarg; requires_world::Bool=false)
9090
wrapper *= "$nargs\n "
9191
argcall = ""
9292
for i = 1:nargs
93-
argcall *= "@lookup(frame, args[$(i+1)])"
93+
argcall *= "lookup(frame, args[$(i+1)])"
9494
if i < nargs
9595
argcall *= ", "
9696
end
@@ -144,7 +144,7 @@ function getargs(args, frame)
144144
nargs = length(args)-1 # skip f
145145
callargs = resize!(frame.framedata.callargs, nargs)
146146
for i = 1:nargs
147-
callargs[i] = @lookup(frame, args[i+1])
147+
callargs[i] = lookup(frame, args[i+1])
148148
end
149149
return callargs
150150
end
@@ -175,7 +175,7 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool)
175175
if isa(fex, QuoteNode)
176176
f = fex.value
177177
else
178-
f = @lookup(frame, fex)
178+
f = lookup(frame, fex)
179179
end
180180
181181
if f isa Core.OpaqueClosure
@@ -208,7 +208,7 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool)
208208
print(io,
209209
"""
210210
$head f === tuple
211-
return Some{Any}(ntupleany(i->@lookup(frame, args[i+1]), length(args)-1))
211+
return Some{Any}(ntupleany(i::Int->lookup(frame, args[i+1]), length(args)-1))
212212
""")
213213
continue
214214
elseif f === Core._apply_iterate
@@ -293,13 +293,13 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool)
293293
if nargs == 1
294294
call_expr = copy(call_expr)
295295
args2 = args[2]
296-
call_expr.args[2] = isa(args2, QuoteNode) ? args2 : @lookup(frame, args2)
296+
call_expr.args[2] = isa(args2, QuoteNode) ? args2 : lookup(frame, args2)
297297
return Some{Any}(Core.eval(moduleof(frame), call_expr))
298298
elseif nargs == 2
299299
call_expr = copy(call_expr)
300300
args2 = args[2]
301-
call_expr.args[2] = isa(args2, QuoteNode) ? args2 : @lookup(frame, args2)
302-
call_expr.args[3] = @lookup(frame, args[3])
301+
call_expr.args[2] = isa(args2, QuoteNode) ? args2 : lookup(frame, args2)
302+
call_expr.args[3] = lookup(frame, args[3])
303303
return Some{Any}(Core.eval(moduleof(frame), call_expr))
304304
end
305305
""")

docs/src/dev_reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ JuliaInterpreter.interpreted_methods
9999

100100
```@docs
101101
JuliaInterpreter.eval_code
102-
JuliaInterpreter.@lookup
102+
JuliaInterpreter.lookup
103103
JuliaInterpreter.is_wrapper_call
104104
JuliaInterpreter.is_doc_expr
105105
JuliaInterpreter.is_global_ref

src/builtins.jl

Lines changed: 98 additions & 98 deletions
Large diffs are not rendered by default.

src/interpret.jl

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,33 @@ function lookup_var(frame::Frame, slot::SlotNumber)
99
throw(UndefVarError(frame.framecode.src.slotnames[slot.id]))
1010
end
1111

12+
"""
13+
lookup(frame::Frame, node)
14+
15+
Looks up previously-computed values referenced as `SSAValue`, `SlotNumber`,
16+
`GlobalRef`, sparam or exception reference expression.
17+
It will also lookup `Symbol`s as global reference in the context of `moduleof(frame)::Module`.
18+
If none of the above apply, the value of `node` will be returned.
19+
"""
20+
function lookup(frame::Frame, @nospecialize(node))
21+
if isa(node, SSAValue)
22+
return lookup_var(frame, node)
23+
elseif isa(node, GlobalRef)
24+
return lookup_var(frame, node)
25+
elseif isa(node, SlotNumber)
26+
return lookup_var(frame, node)
27+
elseif isa(node, Symbol)
28+
return @invokelatest getglobal(moduleof(frame), node)
29+
elseif isa(node, Expr)
30+
return lookup_expr(frame, node)
31+
else # fallback
32+
if isa(node, QuoteNode)
33+
return node.value
34+
end
35+
return node
36+
end
37+
end
38+
1239
function gen_lookup_ex(frame, node)
1340
:(let node = $(esc(node))
1441
isa(node, SSAValue) ? lookup_var($(esc(frame)), node) :
@@ -20,21 +47,13 @@ function gen_lookup_ex(frame, node)
2047
node # fallback
2148
end)
2249
end
23-
24-
"""
25-
rhs = @lookup(frame, node)
26-
27-
This macro looks up previously-computed values referenced as SSAValues, SlotNumbers,
28-
GlobalRefs, QuoteNode, sparam or exception reference expression.
29-
It will also lookup symbols in `moduleof(frame)`.
30-
If none of the above apply, the value of `node` itself will be returned.
31-
"""
3250
macro lookup(frame, node)
51+
@warn "`@lookup` at $f:$l is deprecated, use `lookup(frame, node)` instead."
3352
return gen_lookup_ex(frame, node)
3453
end
3554
macro lookup(_, frame, node)
3655
f, l = __source__.file, __source__.line
37-
@warn "`@lookup(mod, frame, node)` at $f:$l is deprecated, use `@lookup(frame, node)` instead."
56+
@warn "`@lookup(mod, frame, node)` at $f:$l is deprecated, use `lookup(frame, node)` instead."
3857
return gen_lookup_ex(frame, node)
3958
end
4059

@@ -52,13 +71,13 @@ function lookup_expr(frame::Frame, e::Expr)
5271
end
5372
head === :boundscheck && length(e.args) == 0 && return true
5473
if head === :call
55-
f = @lookup frame e.args[1]
74+
f = lookup(frame, e.args[1])
5675
if (@static VERSION < v"1.11.0-DEV.1180" && true) && f === Core.svec
5776
# work around for a linearization bug in Julia (https://github.com/JuliaLang/julia/pull/52497)
58-
return f(Any[@lookup(frame, e.args[i]) for i in 2:length(e.args)]...)
77+
return f(Any[lookup(frame, e.args[i]) for i in 2:length(e.args)]...)
5978
elseif f === Core.tuple
6079
# handling for ccall literal syntax
61-
return f(Any[@lookup(frame, e.args[i]) for i in 2:length(e.args)]...)
80+
return f(Any[lookup(frame, e.args[i]) for i in 2:length(e.args)]...)
6281
end
6382
end
6483
error("invalid lookup expr ", e)
@@ -143,12 +162,12 @@ end
143162
function collect_args(@nospecialize(recurse), frame::Frame, call_expr::Expr; isfc::Bool=false)
144163
args = frame.framedata.callargs
145164
resize!(args, length(call_expr.args))
146-
args[1] = isfc ? resolvefc(frame, call_expr.args[1]) : @lookup(frame, call_expr.args[1])
165+
args[1] = isfc ? resolvefc(frame, call_expr.args[1]) : lookup(frame, call_expr.args[1])
147166
for i = 2:length(args)
148167
if isexpr(call_expr.args[i], :call)
149168
args[i] = lookup_or_eval(recurse, frame, call_expr.args[i])
150169
else
151-
args[i] = @lookup(frame, call_expr.args[i])
170+
args[i] = lookup(frame, call_expr.args[i])
152171
end
153172
end
154173
return args
@@ -318,17 +337,17 @@ function evaluate_methoddef(frame::Frame, node::Expr)
318337
end
319338
end
320339
length(node.args) == 1 && return f
321-
sig = @lookup(frame, node.args[2])::SimpleVector
322-
body = @lookup(frame, node.args[3])::Union{CodeInfo, Expr}
340+
sig = lookup(frame, node.args[2])::SimpleVector
341+
body = lookup(frame, node.args[3])::Union{CodeInfo, Expr}
323342
method = ccall(:jl_method_def, Any, (Any, Ptr{Cvoid}, Any, Any), sig, C_NULL, body, moduleof(frame)::Module)::Method
324343
return method
325344
end
326345

327346
function evaluate_overlayed_methoddef(frame::Frame, node::Expr, mt::MethodTable)
328347
# Overlaying an empty function such as `function f end` is not legal, and `f` must
329348
# already be defined so we don't need to do as much work as in `evaluate_methoddef`.
330-
sig = @lookup(frame, node.args[2])::SimpleVector
331-
body = @lookup(frame, node.args[3])::Union{CodeInfo, Expr}
349+
sig = lookup(frame, node.args[2])::SimpleVector
350+
body = lookup(frame, node.args[3])::Union{CodeInfo, Expr}
332351
method = ccall(:jl_method_def, Any, (Any, Any, Any, Any), sig, mt, body, moduleof(frame)::Module)::Method
333352
return method
334353
end
@@ -382,13 +401,13 @@ maybe_assign!(frame::Frame, @nospecialize(val)) = maybe_assign!(frame, pc_expr(f
382401
function eval_rhs(@nospecialize(recurse), frame::Frame, node::Expr)
383402
head = node.head
384403
if head === :new
385-
args = Any[@lookup(frame, arg) for arg in node.args]
404+
args = Any[lookup(frame, arg) for arg in node.args]
386405
T = popfirst!(args)::DataType
387406
rhs = ccall(:jl_new_structv, Any, (Any, Ptr{Any}, UInt32), T, args, length(args))
388407
return rhs
389408
elseif head === :splatnew # Julia 1.2+
390-
T = @lookup(frame, node.args[1])::DataType
391-
args = @lookup(frame, node.args[2])::Tuple
409+
T = lookup(frame, node.args[1])::DataType
410+
args = lookup(frame, node.args[2])::Tuple
392411
rhs = ccall(:jl_new_structt, Any, (Any, Any), T, args)
393412
return rhs
394413
elseif head === :isdefined
@@ -484,7 +503,7 @@ function step_expr!(@nospecialize(recurse), frame::Frame, @nospecialize(node), i
484503
if isa(rhs, Expr)
485504
rhs = eval_rhs(recurse, frame, rhs)
486505
else
487-
rhs = @lookup(frame, rhs)
506+
rhs = lookup(frame, rhs)
488507
end
489508
isa(rhs, BreakpointRef) && return rhs
490509
do_assignment!(frame, lhs, rhs)
@@ -522,7 +541,7 @@ function step_expr!(@nospecialize(recurse), frame::Frame, @nospecialize(node), i
522541
elseif node.head === :const || node.head === :globaldecl
523542
g = node.args[1]
524543
if length(node.args) == 2
525-
Core.eval(moduleof(frame), Expr(:block, Expr(node.head, g, @lookup(frame, node.args[2])), nothing))
544+
Core.eval(moduleof(frame), Expr(:block, Expr(node.head, g, lookup(frame, node.args[2])), nothing))
526545
else
527546
Core.eval(moduleof(frame), Expr(:block, Expr(node.head, g), nothing))
528547
end
@@ -572,7 +591,7 @@ function step_expr!(@nospecialize(recurse), frame::Frame, @nospecialize(node), i
572591
@assert is_leaf(frame)
573592
return (frame.pc = node.label)
574593
elseif isa(node, GotoIfNot)
575-
arg = @lookup(frame, node.cond)
594+
arg = lookup(frame, node.cond)
576595
if !isa(arg, Bool)
577596
throw(TypeError(nameof(frame), "if", Bool, arg))
578597
end
@@ -591,10 +610,10 @@ function step_expr!(@nospecialize(recurse), frame::Frame, @nospecialize(node), i
591610
rhs = node.catch_dest
592611
push!(data.exception_frames, rhs)
593612
if isdefined(node, :scope)
594-
push!(data.current_scopes, @lookup(frame, node.scope))
613+
push!(data.current_scopes, lookup(frame, node.scope))
595614
end
596615
else
597-
rhs = @lookup(frame, node)
616+
rhs = lookup(frame, node)
598617
end
599618
catch err
600619
return handle_err(recurse, frame, err)
@@ -670,7 +689,7 @@ function handle_err(@nospecialize(recurse), frame::Frame, @nospecialize(err))
670689
return pc
671690
end
672691

673-
lookup_return(frame, node::ReturnNode) = @lookup(frame, node.val)
692+
lookup_return(frame::Frame, node::ReturnNode) = lookup(frame, node.val)
674693

675694
"""
676695
ret = get_return(frame)

src/optimize.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ function build_compiled_llvmcall!(stmt::Expr, code::CodeInfo, idx::Int, evalmod:
186186
pc === nothing && error("this should never happen")
187187
end
188188
end
189-
llvmir, RetType, ArgType = @lookup(frame, stmt.args[2]), @lookup(frame, stmt.args[3]), @lookup(frame, stmt.args[4])::DataType
189+
llvmir, RetType, ArgType = lookup(frame, stmt.args[2]), lookup(frame, stmt.args[3]), lookup(frame, stmt.args[4])::DataType
190190
args = stmt.args[5:end]
191191
argnames = Any[Symbol(:arg, i) for i = 1:length(args)]
192192
cc_key = (llvmir, RetType, ArgType, evalmod) # compiled call key

test/debug.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using CodeTracking, JuliaInterpreter, Test
2-
using JuliaInterpreter: enter_call, enter_call_expr, get_return, @lookup
2+
using JuliaInterpreter: enter_call, enter_call_expr, get_return
33
using Base.Meta: isexpr
44
include("utils.jl")
55

@@ -275,7 +275,7 @@ end
275275
frame = fr = JuliaInterpreter.enter_call(f_va_outer, 1)
276276
# depending on whether this is in or out of a @testset, the first statement may differ
277277
stmt1 = fr.framecode.src.code[1]
278-
if isexpr(stmt1, :call) && @lookup(frame, stmt1.args[1]) === getfield
278+
if isexpr(stmt1, :call) && JuliaInterpreter.lookup(frame, stmt1.args[1]) === getfield
279279
fr, pc = debug_command(fr, :se)
280280
end
281281
fr, pc = debug_command(fr, :s)
@@ -462,7 +462,7 @@ end
462462
frame = JuliaInterpreter.enter_call(f, 2, 3) # at sin
463463
frame, pc = debug_command(frame, :n)
464464
# Check that we are at the kw call to g
465-
@test Core.kwfunc(g) == JuliaInterpreter.@lookup frame JuliaInterpreter.pc_expr(frame).args[1]
465+
@test Core.kwfunc(g) == JuliaInterpreter.lookup(frame, JuliaInterpreter.pc_expr(frame).args[1])
466466
# Step into the inner g
467467
frame, pc = debug_command(frame, :s)
468468
# Finish the frame and make sure we step out of the wrapper
@@ -477,7 +477,7 @@ end
477477
frame = JuliaInterpreter.enter_call(h_1, 2, 1)
478478
frame, pc = debug_command(frame, :s)
479479
# Should have skipped the kwprep in h_2 and be at call to kwfunc h_3
480-
@test Core.kwfunc(h_3) == JuliaInterpreter.@lookup frame JuliaInterpreter.pc_expr(frame).args[1]
480+
@test Core.kwfunc(h_3) == JuliaInterpreter.lookup(frame, JuliaInterpreter.pc_expr(frame).args[1])
481481
end
482482

483483
@testset "si should not step through wrappers or kwprep" begin

test/utils.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using JuliaInterpreter
2-
using JuliaInterpreter: Frame, @lookup
2+
using JuliaInterpreter: Frame
33
using JuliaInterpreter: finish_and_return!, evaluate_call!, step_expr!, shouldbreak,
44
do_assignment!, SSAValue, isassign, pc_expr, handle_err, get_return,
55
moduleof

0 commit comments

Comments
 (0)