Skip to content

Commit 5d7a838

Browse files
committed
Eliminate dynamic dispatch to improve performance
1 parent 4cc3685 commit 5d7a838

File tree

5 files changed

+26
-18
lines changed

5 files changed

+26
-18
lines changed

src/breakpoints.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,10 @@ function add_breakpoint(framecode, stmtidx)
2121
return bp
2222
end
2323

24-
function shouldbreak(frame, pc=frame.pc)
25-
idx = pc
24+
function shouldbreak(frame::Frame, pc::Int)
2625
bps = frame.framecode.breakpoints
27-
isassigned(bps, idx) || return false
28-
bp = bps[idx]
26+
isassigned(bps, pc) || return false
27+
bp = bps[pc]
2928
bp.isactive || return false
3029
return Base.invokelatest(bp.condition, frame)::Bool
3130
end

src/commands.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ function finish!(@nospecialize(recurse), frame::Frame, istoplevel::Bool=false)
1313
local pc
1414
while true
1515
pc = step_expr!(recurse, frame, istoplevel)
16-
(pc == nothing || isa(pc, BreakpointRef)) && return pc
16+
(pc === nothing || isa(pc, BreakpointRef)) && return pc
1717
shouldbreak(frame, pc) && return BreakpointRef(frame.framecode, pc)
1818
end
1919
end
@@ -68,7 +68,7 @@ function finish_stack!(@nospecialize(recurse), frame::Frame, istoplevel::Bool=fa
6868
end
6969
pc += 1
7070
frame.pc = pc
71-
shouldbreak(frame) && return BreakpointRef(frame.framecode, pc)
71+
shouldbreak(frame, pc) && return BreakpointRef(frame.framecode, pc)
7272
end
7373
end
7474
finish_stack!(frame::Frame, istoplevel::Bool=false) = finish_stack!(finish_and_return!, frame, istoplevel)

src/construct.jl

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -289,12 +289,11 @@ end
289289

290290
"""
291291
frame = prepare_frame(framecode::FrameCode, frameargs, lenv)
292-
frame = prepare_frame(caller::Frame, framecode::FrameCode, frameargs, lenv)
293292
294293
Construct a new `Frame` for `framecode`, given lowered-code arguments `frameargs` and
295294
static parameters `lenv`. See [`JuliaInterpreter.prepare_call`](@ref) for information about how to prepare the inputs.
296295
"""
297-
function prepare_frame(framecode, args, lenv)
296+
function prepare_frame(framecode::FrameCode, args::Vector{Any}, lenv::SimpleVector)
298297
framedata = prepare_framedata(framecode, args)
299298
resize!(framedata.sparams, length(lenv))
300299
# Add static parameters to environment
@@ -306,7 +305,7 @@ function prepare_frame(framecode, args, lenv)
306305
return Frame(framecode, framedata)
307306
end
308307

309-
function prepare_frame(caller::Frame, framecode, args, lenv)
308+
function prepare_frame_caller(caller::Frame, framecode::FrameCode, args::Vector{Any}, lenv::SimpleVector)
310309
caller.callee = frame = prepare_frame(framecode, args, lenv)
311310
frame.caller = caller
312311
return frame

src/interpret.jl

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ function bypass_builtins(frame, call_expr, pc)
173173
return nothing
174174
end
175175

176-
function evaluate_call!(::Compiled, frame::Frame, call_expr::Expr)
176+
function evaluate_call_compiled!(::Compiled, frame::Frame, call_expr::Expr)
177177
pc = frame.pc
178178
ret = bypass_builtins(frame, call_expr, pc)
179179
isa(ret, Some{Any}) && return ret.value
@@ -185,7 +185,7 @@ function evaluate_call!(::Compiled, frame::Frame, call_expr::Expr)
185185
return f(fargs...)
186186
end
187187

188-
function evaluate_call!(@nospecialize(recurse::Function), frame::Frame, call_expr::Expr)
188+
function evaluate_call_recurse!(@nospecialize(recurse), frame::Frame, call_expr::Expr)
189189
pc = frame.pc
190190
ret = bypass_builtins(frame, call_expr, pc)
191191
isa(ret, Some{Any}) && return ret.value
@@ -206,15 +206,21 @@ function evaluate_call!(@nospecialize(recurse::Function), frame::Frame, call_exp
206206
end
207207
return framecode # this was a Builtin
208208
end
209-
newframe = prepare_frame(frame, framecode, fargs, lenv)
210-
shouldbreak(newframe) && return BreakpointRef(newframe.framecode, newframe.pc)
211-
ret = recurse(recurse, newframe) # if this errors, handle_err will pop the stack and recycle newframe
209+
newframe = prepare_frame_caller(frame, framecode, fargs, lenv)
210+
npc = newframe.pc
211+
shouldbreak(newframe, npc) && return BreakpointRef(newframe.framecode, npc)
212+
# if the following errors, handle_err will pop the stack and recycle newframe
213+
if recurse === finish_and_return!
214+
# Optimize this case to avoid dynamic dispatch
215+
ret = finish_and_return!(finish_and_return!, newframe, false)
216+
else
217+
ret = recurse(recurse, newframe, false)
218+
end
212219
isa(ret, BreakpointRef) && return ret
213220
frame.callee = nothing
214221
recycle(newframe)
215222
return ret
216223
end
217-
evaluate_call!(frame::Frame, call_expr::Expr) = evaluate_call!(finish_and_return!, frame, call_expr)
218224

219225
"""
220226
ret = evaluate_call!(Compiled(), frame::Frame, call_expr)
@@ -225,7 +231,9 @@ The first causes it to be executed using Julia's normal dispatch (compiled code)
225231
whereas the second recurses in via the interpreter.
226232
`recurse` has a default value of [`JuliaInterpreter.finish_and_return!`](@ref).
227233
"""
228-
evaluate_call!
234+
evaluate_call!(::Compiled, frame::Frame, call_expr::Expr) = evaluate_call_compiled!(Compiled(), frame, call_expr)
235+
evaluate_call!(@nospecialize(recurse), frame::Frame, call_expr::Expr) = evaluate_call_recurse!(recurse, frame, call_expr)
236+
evaluate_call!(frame::Frame, call_expr::Expr) = evaluate_call!(finish_and_return!, frame, call_expr)
229237

230238
# The following come up only when evaluating toplevel code
231239
function evaluate_methoddef(frame, node)
@@ -317,7 +325,9 @@ function eval_rhs(@nospecialize(recurse), frame, node::Expr)
317325
elseif head == :isdefined
318326
return check_isdefined(frame, node.args[1])
319327
elseif head == :call
320-
return evaluate_call!(recurse, frame, node)
328+
# here it's crucial to avoid dynamic dispatch
329+
isa(recurse, Compiled) && return evaluate_call_compiled!(recurse, frame, node)
330+
return evaluate_call_recurse!(recurse, frame, node)
321331
elseif head == :foreigncall || head == :cfunction
322332
return evaluate_foreigncall(frame, node)
323333
elseif head == :copyast

test/utils.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Run `frame` until one of:
4141
"""
4242
function evaluate_limited!(@nospecialize(recurse), frame::Frame, nstmts::Int, istoplevel::Bool=false)
4343
refnstmts = Ref(nstmts)
44-
limexec!(s,f) = limited_exec!(s, f, refnstmts, istoplevel)
44+
limexec!(s, f, istl) = limited_exec!(s, f, refnstmts, istl)
4545
# The following is like finish!, except we intercept :call expressions so that we can run them
4646
# with limexec! rather than the default finish_and_return!
4747
pc = frame.pc

0 commit comments

Comments
 (0)