diff --git a/Project.toml b/Project.toml index 227bce82..2d3a9057 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "JuliaInterpreter" uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a" -version = "0.9.46" +version = "0.10.0" [deps] CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" diff --git a/bin/generate_builtins.jl b/bin/generate_builtins.jl index f68d6bce..c679862a 100644 --- a/bin/generate_builtins.jl +++ b/bin/generate_builtins.jl @@ -41,7 +41,7 @@ const REQUIRES_WORLD = Core.Builtin[ replaceglobal!, setglobalonce!, ] -const CALL_LATEST = """args = getargs(args, frame) +const CALL_LATEST = """args = getargs(interp, args, frame) if !expand return Some{Any}(Core._call_latest(args...)) end @@ -50,7 +50,7 @@ const CALL_LATEST = """args = getargs(args, frame) for x in args push!(new_expr.args, QuoteNode(x)) end - return maybe_recurse_expanded_builtin(frame, new_expr) + return maybe_recurse_expanded_builtin(interp, frame, new_expr) """ function scopedname(f) @@ -90,7 +90,7 @@ function generate_fcall_nargs(fname, minarg, maxarg; requires_world::Bool=false) wrapper *= "$nargs\n " argcall = "" for i = 1:nargs - argcall *= "lookup(frame, args[$(i+1)])" + argcall *= "lookup(interp, frame, args[$(i+1)])" if i < nargs argcall *= ", " end @@ -104,9 +104,9 @@ function generate_fcall_nargs(fname, minarg, maxarg; requires_world::Bool=false) wrapper *= "\n else" # To throw the correct error if requires_world - wrapper *= "\n return Some{Any}(Base.invoke_in_world(frame.world, $fname, getargs(args, frame)...)$annotation)" + wrapper *= "\n return Some{Any}(Base.invoke_in_world(frame.world, $fname, getargs(interp, args, frame)...)$annotation)" else - wrapper *= "\n return Some{Any}($fname(getargs(args, frame)...)$annotation)" + wrapper *= "\n return Some{Any}($fname(getargs(interp, args, frame)...)$annotation)" end wrapper *= "\n end" return wrapper @@ -122,9 +122,9 @@ function generate_fcall(f, table, id) # A built-in with arbitrary or unknown number of arguments. # This will (unfortunately) use dynamic dispatch. if requires_world - return "return Some{Any}(Base.invoke_in_world(frame.world, $fname, getargs(args, frame)...))" + return "return Some{Any}(Base.invoke_in_world(frame.world, $fname, getargs(interp, args, frame)...))" end - return "return Some{Any}($fname(getargs(args, frame)...))" + return "return Some{Any}($fname(getargs(interp, args, frame)...))" end # `io` is for the generated source file @@ -140,42 +140,42 @@ function generate_builtins(io::IO) """ # This file is generated by `generate_builtins.jl`. Do not edit by hand. -function getargs(args, frame) +function getargs(interp::Interpreter, args::Vector{Any}, frame::Frame) nargs = length(args)-1 # skip f callargs = resize!(frame.framedata.callargs, nargs) for i = 1:nargs - callargs[i] = lookup(frame, args[i+1]) + callargs[i] = lookup(interp, frame, args[i+1]) end return callargs end const kwinvoke = Core.kwfunc(Core.invoke) -function maybe_recurse_expanded_builtin(frame, new_expr) +function maybe_recurse_expanded_builtin(interp::Interpreter, frame::Frame, new_expr::Expr) f = new_expr.args[1] if isa(f, Core.Builtin) || isa(f, Core.IntrinsicFunction) - return maybe_evaluate_builtin(frame, new_expr, true) + return maybe_evaluate_builtin(interp, frame, new_expr, true) else return new_expr end end \"\"\" - ret = maybe_evaluate_builtin(frame, call_expr, expand::Bool) + ret = maybe_evaluate_builtin(interp::Interpreter, frame::Frame, call_expr::Expr, expand::Bool) If `call_expr` is to a builtin function, evaluate it, returning the result inside a `Some` wrapper. Otherwise, return `call_expr`. If `expand` is true, `Core._apply_iterate` calls will be resolved as a call to the applied function. \"\"\" -function maybe_evaluate_builtin(frame, call_expr, expand::Bool) +function maybe_evaluate_builtin(interp::Interpreter, frame::Frame, call_expr::Expr, expand::Bool) args = call_expr.args nargs = length(args) - 1 fex = args[1] if isa(fex, QuoteNode) f = fex.value else - f = lookup(frame, fex) + f = lookup(interp, frame, fex) end if f isa Core.OpaqueClosure @@ -208,7 +208,7 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool) print(io, """ $head f === tuple - return Some{Any}(ntupleany(i::Int->lookup(frame, args[i+1]), length(args)-1)) + return Some{Any}(ntupleany(i::Int->lookup(interp, frame, args[i+1]), length(args)-1)) """) continue elseif f === Core._apply_iterate @@ -216,7 +216,7 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool) print(io, """ $head f === Core._apply_iterate - argswrapped = getargs(args, frame) + argswrapped = getargs(interp, args, frame) if !expand return Some{Any}(Core._apply_iterate(argswrapped...)) end @@ -229,7 +229,7 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool) for x in argsflat push!(new_expr.args, QuoteNode(x)) end - return maybe_recurse_expanded_builtin(frame, new_expr) + return maybe_recurse_expanded_builtin(interp, frame, new_expr) """) continue elseif f === Core.invoke @@ -238,7 +238,7 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool) """ $head f === $fstr if !expand - argswrapped = getargs(args, frame) + argswrapped = getargs(interp, args, frame) return Some{Any}($fstr(argswrapped...)) end # This uses the original arguments to avoid looking them up twice @@ -257,7 +257,7 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool) end return Some{Any}(currscope) else - return Some{Any}(Core.current_scope(getargs(args, frame)...)) + return Some{Any}(Core.current_scope(getargs(interp, args, frame)...)) end """) continue @@ -293,13 +293,13 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool) if nargs == 1 call_expr = copy(call_expr) args2 = args[2] - call_expr.args[2] = isa(args2, QuoteNode) ? args2 : lookup(frame, args2) + call_expr.args[2] = isa(args2, QuoteNode) ? args2 : lookup(interp, frame, args2) return Some{Any}(Core.eval(moduleof(frame), call_expr)) elseif nargs == 2 call_expr = copy(call_expr) args2 = args[2] - call_expr.args[2] = isa(args2, QuoteNode) ? args2 : lookup(frame, args2) - call_expr.args[3] = lookup(frame, args[3]) + call_expr.args[2] = isa(args2, QuoteNode) ? args2 : lookup(interp, frame, args2) + call_expr.args[3] = lookup(interp, frame, args[3]) return Some{Any}(Core.eval(moduleof(frame), call_expr)) end """) @@ -322,7 +322,7 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool) _scopedname = "$mod.$name" fcall = name === :_call_latest ? CALL_LATEST : maxarg < typemax(Int) ? generate_fcall_nargs(_scopedname, minarg, maxarg) : - "return Some{Any}($_scopedname(getargs(args, frame)...))" + "return Some{Any}($_scopedname(getargs(interp, args, frame)...))" rname = repr(name) print(io, """ @@ -361,7 +361,7 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool) print(io, """ if isa(f, Core.IntrinsicFunction) - cargs = getargs(args, frame) + cargs = getargs(interp, args, frame) if f === Core.Intrinsics.have_fma && length(cargs) == 1 cargs1 = cargs[1] if cargs1 == Float64 @@ -392,7 +392,7 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool) """ end if isa(f, typeof(kwinvoke)) - return Some{Any}(kwinvoke(getargs(args, frame)...)) + return Some{Any}(kwinvoke(getargs(interp, args, frame)...)) end return call_expr end diff --git a/docs/src/dev_reference.md b/docs/src/dev_reference.md index 55bf5f04..e9a4f199 100644 --- a/docs/src/dev_reference.md +++ b/docs/src/dev_reference.md @@ -6,6 +6,15 @@ @interpret ``` +## Interpreter + +```@docs +JuliaInterpreter.Interpreter +JuliaInterpreter.RecursiveInterpreter +JuliaInterpreter.NonRecursiveInterpreter +JuliaInterpreter.Compiled +``` + ## Frame creation ```@docs @@ -31,11 +40,11 @@ leaf ## Frame execution ```@docs -JuliaInterpreter.Compiled JuliaInterpreter.step_expr! JuliaInterpreter.finish! JuliaInterpreter.finish_and_return! JuliaInterpreter.finish_stack! +JuliaInterpreter.finish_nested_frame! JuliaInterpreter.get_return JuliaInterpreter.next_until! JuliaInterpreter.maybe_next_until! @@ -67,7 +76,7 @@ toggle break_on break_off breakpoints -JuliaInterpreter.dummy_breakpoint +JuliaInterpreter.BreakOnCall ``` ## Types diff --git a/src/builtins.jl b/src/builtins.jl index 36ae00a7..fb7f5688 100644 --- a/src/builtins.jl +++ b/src/builtins.jl @@ -1,41 +1,41 @@ # This file is generated by `generate_builtins.jl`. Do not edit by hand. -function getargs(args, frame) +function getargs(interp::Interpreter, args::Vector{Any}, frame::Frame) nargs = length(args)-1 # skip f callargs = resize!(frame.framedata.callargs, nargs) for i = 1:nargs - callargs[i] = lookup(frame, args[i+1]) + callargs[i] = lookup(interp, frame, args[i+1]) end return callargs end const kwinvoke = Core.kwfunc(Core.invoke) -function maybe_recurse_expanded_builtin(frame, new_expr) +function maybe_recurse_expanded_builtin(interp::Interpreter, frame::Frame, new_expr::Expr) f = new_expr.args[1] if isa(f, Core.Builtin) || isa(f, Core.IntrinsicFunction) - return maybe_evaluate_builtin(frame, new_expr, true) + return maybe_evaluate_builtin(interp, frame, new_expr, true) else return new_expr end end """ - ret = maybe_evaluate_builtin(frame, call_expr, expand::Bool) + ret = maybe_evaluate_builtin(interp::Interpreter, frame::Frame, call_expr::Expr, expand::Bool) If `call_expr` is to a builtin function, evaluate it, returning the result inside a `Some` wrapper. Otherwise, return `call_expr`. If `expand` is true, `Core._apply_iterate` calls will be resolved as a call to the applied function. """ -function maybe_evaluate_builtin(frame, call_expr, expand::Bool) +function maybe_evaluate_builtin(interp::Interpreter, frame::Frame, call_expr::Expr, expand::Bool) args = call_expr.args nargs = length(args) - 1 fex = args[1] if isa(fex, QuoteNode) f = fex.value else - f = lookup(frame, fex) + f = lookup(interp, frame, fex) end if f isa Core.OpaqueClosure @@ -55,20 +55,20 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool) # each gets call-site optimized. if f === <: if nargs == 2 - return Some{Any}(<:(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(<:(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) else - return Some{Any}(<:(getargs(args, frame)...)) + return Some{Any}(<:(getargs(interp, args, frame)...)) end elseif f === === if nargs == 2 - return Some{Any}(===(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(===(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) else - return Some{Any}(===(getargs(args, frame)...)) + return Some{Any}(===(getargs(interp, args, frame)...)) end elseif f === Core._abstracttype - return Some{Any}(Core._abstracttype(getargs(args, frame)...)) + return Some{Any}(Core._abstracttype(getargs(interp, args, frame)...)) elseif f === Core._apply_iterate - argswrapped = getargs(args, frame) + argswrapped = getargs(interp, args, frame) if !expand return Some{Any}(Core._apply_iterate(argswrapped...)) end @@ -81,44 +81,44 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool) for x in argsflat push!(new_expr.args, QuoteNode(x)) end - return maybe_recurse_expanded_builtin(frame, new_expr) + return maybe_recurse_expanded_builtin(interp, frame, new_expr) elseif f === Core._call_in_world_total - return Some{Any}(Core._call_in_world_total(getargs(args, frame)...)) + return Some{Any}(Core._call_in_world_total(getargs(interp, args, frame)...)) elseif f === Core._compute_sparams - return Some{Any}(Core._compute_sparams(getargs(args, frame)...)) + return Some{Any}(Core._compute_sparams(getargs(interp, args, frame)...)) elseif @static isdefinedglobal(Core, :_defaultctors) && f === Core._defaultctors - return Some{Any}(Core._defaultctors(getargs(args, frame)...)) + return Some{Any}(Core._defaultctors(getargs(interp, args, frame)...)) elseif f === Core._equiv_typedef - return Some{Any}(Core._equiv_typedef(getargs(args, frame)...)) + return Some{Any}(Core._equiv_typedef(getargs(interp, args, frame)...)) elseif f === Core._expr - return Some{Any}(Core._expr(getargs(args, frame)...)) + return Some{Any}(Core._expr(getargs(interp, args, frame)...)) elseif f === Core._primitivetype - return Some{Any}(Core._primitivetype(getargs(args, frame)...)) + return Some{Any}(Core._primitivetype(getargs(interp, args, frame)...)) elseif f === Core._setsuper! - return Some{Any}(Core._setsuper!(getargs(args, frame)...)) + return Some{Any}(Core._setsuper!(getargs(interp, args, frame)...)) elseif f === Core._structtype - return Some{Any}(Core._structtype(getargs(args, frame)...)) + return Some{Any}(Core._structtype(getargs(interp, args, frame)...)) elseif f === Core._svec_ref if nargs == 2 - return Some{Any}(Core._svec_ref(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(Core._svec_ref(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) else - return Some{Any}(Core._svec_ref(getargs(args, frame)...)) + return Some{Any}(Core._svec_ref(getargs(interp, args, frame)...)) end elseif f === Core._typebody! - return Some{Any}(Core._typebody!(getargs(args, frame)...)) + return Some{Any}(Core._typebody!(getargs(interp, args, frame)...)) elseif f === Core._typevar if nargs == 3 - return Some{Any}(Core._typevar(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(Core._typevar(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) else - return Some{Any}(Core._typevar(getargs(args, frame)...)) + return Some{Any}(Core._typevar(getargs(interp, args, frame)...)) end elseif f === Core.apply_type - return Some{Any}(Core.apply_type(getargs(args, frame)...)) + return Some{Any}(Core.apply_type(getargs(interp, args, frame)...)) elseif f === Core.compilerbarrier if nargs == 2 - return Some{Any}(Core.compilerbarrier(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(Core.compilerbarrier(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) else - return Some{Any}(Core.compilerbarrier(getargs(args, frame)...)) + return Some{Any}(Core.compilerbarrier(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :current_scope) && f === Core.current_scope if nargs == 0 @@ -128,150 +128,150 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool) end return Some{Any}(currscope) else - return Some{Any}(Core.current_scope(getargs(args, frame)...)) + return Some{Any}(Core.current_scope(getargs(interp, args, frame)...)) end elseif f === Core.donotdelete - return Some{Any}(Core.donotdelete(getargs(args, frame)...)) + return Some{Any}(Core.donotdelete(getargs(interp, args, frame)...)) elseif f === Core.finalizer if nargs == 2 - return Some{Any}(Core.finalizer(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(Core.finalizer(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) elseif nargs == 3 - return Some{Any}(Core.finalizer(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(Core.finalizer(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) elseif nargs == 4 - return Some{Any}(Core.finalizer(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(Core.finalizer(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) else - return Some{Any}(Core.finalizer(getargs(args, frame)...)) + return Some{Any}(Core.finalizer(getargs(interp, args, frame)...)) end elseif f === Core.get_binding_type if nargs == 2 - return Some{Any}(Base.invoke_in_world(frame.world, Core.get_binding_type, lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(Base.invoke_in_world(frame.world, Core.get_binding_type, lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) else - return Some{Any}(Base.invoke_in_world(frame.world, Core.get_binding_type, getargs(args, frame)...)) + return Some{Any}(Base.invoke_in_world(frame.world, Core.get_binding_type, getargs(interp, args, frame)...)) end elseif f === Core.ifelse if nargs == 3 - return Some{Any}(Core.ifelse(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(Core.ifelse(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) else - return Some{Any}(Core.ifelse(getargs(args, frame)...)) + return Some{Any}(Core.ifelse(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :invoke_in_world) && f === Core.invoke_in_world - return Some{Any}(Core.invoke_in_world(getargs(args, frame)...)) + return Some{Any}(Core.invoke_in_world(getargs(interp, args, frame)...)) elseif @static isdefinedglobal(Core, :memorynew) && f === Core.memorynew if nargs == 2 - return Some{Any}(Core.memorynew(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(Core.memorynew(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) else - return Some{Any}(Core.memorynew(getargs(args, frame)...)) + return Some{Any}(Core.memorynew(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :memoryref_isassigned) && f === Core.memoryref_isassigned if nargs == 3 - return Some{Any}(Core.memoryref_isassigned(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(Core.memoryref_isassigned(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) else - return Some{Any}(Core.memoryref_isassigned(getargs(args, frame)...)) + return Some{Any}(Core.memoryref_isassigned(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :memoryrefget) && f === Core.memoryrefget if nargs == 3 - return Some{Any}(Core.memoryrefget(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(Core.memoryrefget(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) else - return Some{Any}(Core.memoryrefget(getargs(args, frame)...)) + return Some{Any}(Core.memoryrefget(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :memoryrefmodify!) && f === Core.memoryrefmodify! if nargs == 5 - return Some{Any}(Core.memoryrefmodify!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]))) + return Some{Any}(Core.memoryrefmodify!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]))) else - return Some{Any}(Core.memoryrefmodify!(getargs(args, frame)...)) + return Some{Any}(Core.memoryrefmodify!(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :memoryrefnew) && f === Core.memoryrefnew if nargs == 1 - return Some{Any}(Core.memoryrefnew(lookup(frame, args[2]))) + return Some{Any}(Core.memoryrefnew(lookup(interp, frame, args[2]))) elseif nargs == 2 - return Some{Any}(Core.memoryrefnew(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(Core.memoryrefnew(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) elseif nargs == 3 - return Some{Any}(Core.memoryrefnew(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(Core.memoryrefnew(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) elseif nargs == 4 - return Some{Any}(Core.memoryrefnew(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(Core.memoryrefnew(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) elseif nargs == 5 - return Some{Any}(Core.memoryrefnew(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]))) + return Some{Any}(Core.memoryrefnew(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]))) else - return Some{Any}(Core.memoryrefnew(getargs(args, frame)...)) + return Some{Any}(Core.memoryrefnew(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :memoryrefoffset) && f === Core.memoryrefoffset if nargs == 1 - return Some{Any}(Core.memoryrefoffset(lookup(frame, args[2]))) + return Some{Any}(Core.memoryrefoffset(lookup(interp, frame, args[2]))) else - return Some{Any}(Core.memoryrefoffset(getargs(args, frame)...)) + return Some{Any}(Core.memoryrefoffset(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :memoryrefreplace!) && f === Core.memoryrefreplace! if nargs == 6 - return Some{Any}(Core.memoryrefreplace!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]), lookup(frame, args[7]))) + return Some{Any}(Core.memoryrefreplace!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]), lookup(interp, frame, args[7]))) else - return Some{Any}(Core.memoryrefreplace!(getargs(args, frame)...)) + return Some{Any}(Core.memoryrefreplace!(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :memoryrefset!) && f === Core.memoryrefset! if nargs == 4 - return Some{Any}(Core.memoryrefset!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(Core.memoryrefset!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) else - return Some{Any}(Core.memoryrefset!(getargs(args, frame)...)) + return Some{Any}(Core.memoryrefset!(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :memoryrefsetonce!) && f === Core.memoryrefsetonce! if nargs == 5 - return Some{Any}(Core.memoryrefsetonce!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]))) + return Some{Any}(Core.memoryrefsetonce!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]))) else - return Some{Any}(Core.memoryrefsetonce!(getargs(args, frame)...)) + return Some{Any}(Core.memoryrefsetonce!(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :memoryrefswap!) && f === Core.memoryrefswap! if nargs == 4 - return Some{Any}(Core.memoryrefswap!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(Core.memoryrefswap!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) else - return Some{Any}(Core.memoryrefswap!(getargs(args, frame)...)) + return Some{Any}(Core.memoryrefswap!(getargs(interp, args, frame)...)) end elseif f === Core.sizeof if nargs == 1 - return Some{Any}(Core.sizeof(lookup(frame, args[2]))) + return Some{Any}(Core.sizeof(lookup(interp, frame, args[2]))) else - return Some{Any}(Core.sizeof(getargs(args, frame)...)) + return Some{Any}(Core.sizeof(getargs(interp, args, frame)...)) end elseif f === Core.svec - return Some{Any}(Core.svec(getargs(args, frame)...)) + return Some{Any}(Core.svec(getargs(interp, args, frame)...)) elseif @static isdefinedglobal(Core, :throw_methoderror) && f === Core.throw_methoderror - return Some{Any}(Core.throw_methoderror(getargs(args, frame)...)) + return Some{Any}(Core.throw_methoderror(getargs(interp, args, frame)...)) elseif f === applicable - return Some{Any}(applicable(getargs(args, frame)...)) + return Some{Any}(applicable(getargs(interp, args, frame)...)) elseif f === fieldtype if nargs == 2 - return Some{Any}(fieldtype(lookup(frame, args[2]), lookup(frame, args[3]))::Type) + return Some{Any}(fieldtype(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))::Type) elseif nargs == 3 - return Some{Any}(fieldtype(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))::Type) + return Some{Any}(fieldtype(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))::Type) else - return Some{Any}(fieldtype(getargs(args, frame)...)::Type) + return Some{Any}(fieldtype(getargs(interp, args, frame)...)::Type) end elseif f === getfield if nargs == 2 - return Some{Any}(getfield(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(getfield(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) elseif nargs == 3 - return Some{Any}(getfield(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(getfield(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) elseif nargs == 4 - return Some{Any}(getfield(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(getfield(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) else - return Some{Any}(getfield(getargs(args, frame)...)) + return Some{Any}(getfield(getargs(interp, args, frame)...)) end elseif f === getglobal if nargs == 2 - return Some{Any}(getglobal(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(getglobal(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) elseif nargs == 3 - return Some{Any}(getglobal(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(getglobal(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) else - return Some{Any}(getglobal(getargs(args, frame)...)) + return Some{Any}(getglobal(getargs(interp, args, frame)...)) end elseif f === invoke if !expand - argswrapped = getargs(args, frame) + argswrapped = getargs(interp, args, frame) return Some{Any}(invoke(argswrapped...)) end # This uses the original arguments to avoid looking them up twice # See #442 return Expr(:call, invoke, args[2:end]...) elseif @static isdefinedglobal(Core, :invokelatest) && f === Core.invokelatest - args = getargs(args, frame) + args = getargs(interp, args, frame) if !expand return Some{Any}(Core.invokelatest(args...)) end @@ -280,240 +280,240 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool) for x in args push!(new_expr.args, QuoteNode(x)) end - return maybe_recurse_expanded_builtin(frame, new_expr) + return maybe_recurse_expanded_builtin(interp, frame, new_expr) elseif f === isa if nargs == 2 - return Some{Any}(isa(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(isa(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) else - return Some{Any}(isa(getargs(args, frame)...)) + return Some{Any}(isa(getargs(interp, args, frame)...)) end elseif f === isdefined if nargs == 2 - return Some{Any}(isdefined(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(isdefined(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) elseif nargs == 3 - return Some{Any}(isdefined(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(isdefined(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) else - return Some{Any}(isdefined(getargs(args, frame)...)) + return Some{Any}(isdefined(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :isdefinedglobal) && f === isdefinedglobal - return Some{Any}(isdefinedglobal(getargs(args, frame)...)) + return Some{Any}(isdefinedglobal(getargs(interp, args, frame)...)) elseif f === modifyfield! if nargs == 4 - return Some{Any}(modifyfield!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(modifyfield!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) elseif nargs == 5 - return Some{Any}(modifyfield!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]))) + return Some{Any}(modifyfield!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]))) else - return Some{Any}(modifyfield!(getargs(args, frame)...)) + return Some{Any}(modifyfield!(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :modifyglobal!) && f === modifyglobal! if nargs == 4 - return Some{Any}(Base.invoke_in_world(frame.world, modifyglobal!, lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(Base.invoke_in_world(frame.world, modifyglobal!, lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) elseif nargs == 5 - return Some{Any}(Base.invoke_in_world(frame.world, modifyglobal!, lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]))) + return Some{Any}(Base.invoke_in_world(frame.world, modifyglobal!, lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]))) else - return Some{Any}(Base.invoke_in_world(frame.world, modifyglobal!, getargs(args, frame)...)) + return Some{Any}(Base.invoke_in_world(frame.world, modifyglobal!, getargs(interp, args, frame)...)) end elseif f === nfields if nargs == 1 - return Some{Any}(nfields(lookup(frame, args[2]))) + return Some{Any}(nfields(lookup(interp, frame, args[2]))) else - return Some{Any}(nfields(getargs(args, frame)...)) + return Some{Any}(nfields(getargs(interp, args, frame)...)) end elseif f === replacefield! if nargs == 4 - return Some{Any}(replacefield!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(replacefield!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) elseif nargs == 5 - return Some{Any}(replacefield!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]))) + return Some{Any}(replacefield!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]))) elseif nargs == 6 - return Some{Any}(replacefield!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]), lookup(frame, args[7]))) + return Some{Any}(replacefield!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]), lookup(interp, frame, args[7]))) else - return Some{Any}(replacefield!(getargs(args, frame)...)) + return Some{Any}(replacefield!(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :replaceglobal!) && f === replaceglobal! if nargs == 4 - return Some{Any}(Base.invoke_in_world(frame.world, replaceglobal!, lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(Base.invoke_in_world(frame.world, replaceglobal!, lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) elseif nargs == 5 - return Some{Any}(Base.invoke_in_world(frame.world, replaceglobal!, lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]))) + return Some{Any}(Base.invoke_in_world(frame.world, replaceglobal!, lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]))) elseif nargs == 6 - return Some{Any}(Base.invoke_in_world(frame.world, replaceglobal!, lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]), lookup(frame, args[7]))) + return Some{Any}(Base.invoke_in_world(frame.world, replaceglobal!, lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]), lookup(interp, frame, args[7]))) else - return Some{Any}(Base.invoke_in_world(frame.world, replaceglobal!, getargs(args, frame)...)) + return Some{Any}(Base.invoke_in_world(frame.world, replaceglobal!, getargs(interp, args, frame)...)) end elseif f === setfield! if nargs == 3 - return Some{Any}(setfield!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(setfield!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) elseif nargs == 4 - return Some{Any}(setfield!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(setfield!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) else - return Some{Any}(setfield!(getargs(args, frame)...)) + return Some{Any}(setfield!(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :setfieldonce!) && f === setfieldonce! if nargs == 3 - return Some{Any}(setfieldonce!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(setfieldonce!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) elseif nargs == 4 - return Some{Any}(setfieldonce!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(setfieldonce!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) elseif nargs == 5 - return Some{Any}(setfieldonce!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]))) + return Some{Any}(setfieldonce!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]))) else - return Some{Any}(setfieldonce!(getargs(args, frame)...)) + return Some{Any}(setfieldonce!(getargs(interp, args, frame)...)) end elseif f === setglobal! if nargs == 3 - return Some{Any}(Base.invoke_in_world(frame.world, setglobal!, lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(Base.invoke_in_world(frame.world, setglobal!, lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) elseif nargs == 4 - return Some{Any}(Base.invoke_in_world(frame.world, setglobal!, lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(Base.invoke_in_world(frame.world, setglobal!, lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) else - return Some{Any}(Base.invoke_in_world(frame.world, setglobal!, getargs(args, frame)...)) + return Some{Any}(Base.invoke_in_world(frame.world, setglobal!, getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :setglobalonce!) && f === setglobalonce! if nargs == 3 - return Some{Any}(Base.invoke_in_world(frame.world, setglobalonce!, lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(Base.invoke_in_world(frame.world, setglobalonce!, lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) elseif nargs == 4 - return Some{Any}(Base.invoke_in_world(frame.world, setglobalonce!, lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(Base.invoke_in_world(frame.world, setglobalonce!, lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) elseif nargs == 5 - return Some{Any}(Base.invoke_in_world(frame.world, setglobalonce!, lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]))) + return Some{Any}(Base.invoke_in_world(frame.world, setglobalonce!, lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]))) else - return Some{Any}(Base.invoke_in_world(frame.world, setglobalonce!, getargs(args, frame)...)) + return Some{Any}(Base.invoke_in_world(frame.world, setglobalonce!, getargs(interp, args, frame)...)) end elseif f === swapfield! if nargs == 3 - return Some{Any}(swapfield!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(swapfield!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) elseif nargs == 4 - return Some{Any}(swapfield!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(swapfield!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) else - return Some{Any}(swapfield!(getargs(args, frame)...)) + return Some{Any}(swapfield!(getargs(interp, args, frame)...)) end elseif @static isdefinedglobal(Core, :swapglobal!) && f === swapglobal! if nargs == 3 - return Some{Any}(Base.invoke_in_world(frame.world, swapglobal!, lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(Base.invoke_in_world(frame.world, swapglobal!, lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) elseif nargs == 4 - return Some{Any}(Base.invoke_in_world(frame.world, swapglobal!, lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(Base.invoke_in_world(frame.world, swapglobal!, lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) else - return Some{Any}(Base.invoke_in_world(frame.world, swapglobal!, getargs(args, frame)...)) + return Some{Any}(Base.invoke_in_world(frame.world, swapglobal!, getargs(interp, args, frame)...)) end elseif f === throw if nargs == 1 - return Some{Any}(throw(lookup(frame, args[2]))) + return Some{Any}(throw(lookup(interp, frame, args[2]))) else - return Some{Any}(throw(getargs(args, frame)...)) + return Some{Any}(throw(getargs(interp, args, frame)...)) end elseif f === tuple - return Some{Any}(ntupleany(i::Int->lookup(frame, args[i+1]), length(args)-1)) + return Some{Any}(ntupleany(i::Int->lookup(interp, frame, args[i+1]), length(args)-1)) elseif f === typeassert if nargs == 2 - return Some{Any}(typeassert(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(typeassert(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) else - return Some{Any}(typeassert(getargs(args, frame)...)) + return Some{Any}(typeassert(getargs(interp, args, frame)...)) end elseif f === typeof if nargs == 1 - return Some{Any}(typeof(lookup(frame, args[2]))) + return Some{Any}(typeof(lookup(interp, frame, args[2]))) else - return Some{Any}(typeof(getargs(args, frame)...)) + return Some{Any}(typeof(getargs(interp, args, frame)...)) end # Intrinsics elseif f === Base.cglobal if nargs == 1 call_expr = copy(call_expr) args2 = args[2] - call_expr.args[2] = isa(args2, QuoteNode) ? args2 : lookup(frame, args2) + call_expr.args[2] = isa(args2, QuoteNode) ? args2 : lookup(interp, frame, args2) return Some{Any}(Core.eval(moduleof(frame), call_expr)) elseif nargs == 2 call_expr = copy(call_expr) args2 = args[2] - call_expr.args[2] = isa(args2, QuoteNode) ? args2 : lookup(frame, args2) - call_expr.args[3] = lookup(frame, args[3]) + call_expr.args[2] = isa(args2, QuoteNode) ? args2 : lookup(interp, frame, args2) + call_expr.args[3] = lookup(interp, frame, args[3]) return Some{Any}(Core.eval(moduleof(frame), call_expr)) end elseif @static (isdefinedglobal(Core, :arrayref) && Core.arrayref isa Core.Builtin) && f === Core.arrayref if nargs == 1 - return Some{Any}(Core.arrayref(lookup(frame, args[2]))) + return Some{Any}(Core.arrayref(lookup(interp, frame, args[2]))) elseif nargs == 2 - return Some{Any}(Core.arrayref(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(Core.arrayref(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) elseif nargs == 3 - return Some{Any}(Core.arrayref(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(Core.arrayref(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) elseif nargs == 4 - return Some{Any}(Core.arrayref(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(Core.arrayref(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) elseif nargs == 5 - return Some{Any}(Core.arrayref(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]))) + return Some{Any}(Core.arrayref(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]))) else - return Some{Any}(Core.arrayref(getargs(args, frame)...)) + return Some{Any}(Core.arrayref(getargs(interp, args, frame)...)) end elseif @static (isdefinedglobal(Core, :arrayset) && Core.arrayset isa Core.Builtin) && f === Core.arrayset if nargs == 1 - return Some{Any}(Core.arrayset(lookup(frame, args[2]))) + return Some{Any}(Core.arrayset(lookup(interp, frame, args[2]))) elseif nargs == 2 - return Some{Any}(Core.arrayset(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(Core.arrayset(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) elseif nargs == 3 - return Some{Any}(Core.arrayset(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(Core.arrayset(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) elseif nargs == 4 - return Some{Any}(Core.arrayset(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(Core.arrayset(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) elseif nargs == 5 - return Some{Any}(Core.arrayset(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]))) + return Some{Any}(Core.arrayset(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]))) elseif nargs == 6 - return Some{Any}(Core.arrayset(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]), lookup(frame, args[7]))) + return Some{Any}(Core.arrayset(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]), lookup(interp, frame, args[7]))) else - return Some{Any}(Core.arrayset(getargs(args, frame)...)) + return Some{Any}(Core.arrayset(getargs(interp, args, frame)...)) end elseif @static (isdefinedglobal(Core, :arrayset) && Core.arrayset isa Core.Builtin) && f === Core.arrayset if nargs == 1 - return Some{Any}(Core.arrayset(lookup(frame, args[2]))) + return Some{Any}(Core.arrayset(lookup(interp, frame, args[2]))) elseif nargs == 2 - return Some{Any}(Core.arrayset(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(Core.arrayset(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) elseif nargs == 3 - return Some{Any}(Core.arrayset(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(Core.arrayset(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) elseif nargs == 4 - return Some{Any}(Core.arrayset(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(Core.arrayset(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) elseif nargs == 5 - return Some{Any}(Core.arrayset(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]))) + return Some{Any}(Core.arrayset(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]))) elseif nargs == 6 - return Some{Any}(Core.arrayset(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]), lookup(frame, args[7]))) + return Some{Any}(Core.arrayset(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]), lookup(interp, frame, args[7]))) else - return Some{Any}(Core.arrayset(getargs(args, frame)...)) + return Some{Any}(Core.arrayset(getargs(interp, args, frame)...)) end elseif @static (isdefinedglobal(Core, :const_arrayref) && Core.const_arrayref isa Core.Builtin) && f === Core.const_arrayref if nargs == 1 - return Some{Any}(Core.const_arrayref(lookup(frame, args[2]))) + return Some{Any}(Core.const_arrayref(lookup(interp, frame, args[2]))) elseif nargs == 2 - return Some{Any}(Core.const_arrayref(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(Core.const_arrayref(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) elseif nargs == 3 - return Some{Any}(Core.const_arrayref(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(Core.const_arrayref(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) elseif nargs == 4 - return Some{Any}(Core.const_arrayref(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(Core.const_arrayref(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) elseif nargs == 5 - return Some{Any}(Core.const_arrayref(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]))) + return Some{Any}(Core.const_arrayref(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]))) else - return Some{Any}(Core.const_arrayref(getargs(args, frame)...)) + return Some{Any}(Core.const_arrayref(getargs(interp, args, frame)...)) end elseif @static (isdefinedglobal(Core, :memoryref) && Core.memoryref isa Core.Builtin) && f === Core.memoryref if nargs == 1 - return Some{Any}(Core.memoryref(lookup(frame, args[2]))) + return Some{Any}(Core.memoryref(lookup(interp, frame, args[2]))) elseif nargs == 2 - return Some{Any}(Core.memoryref(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(Core.memoryref(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) elseif nargs == 3 - return Some{Any}(Core.memoryref(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(Core.memoryref(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) elseif nargs == 4 - return Some{Any}(Core.memoryref(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]))) + return Some{Any}(Core.memoryref(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]))) elseif nargs == 5 - return Some{Any}(Core.memoryref(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]), lookup(frame, args[5]), lookup(frame, args[6]))) + return Some{Any}(Core.memoryref(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]), lookup(interp, frame, args[5]), lookup(interp, frame, args[6]))) else - return Some{Any}(Core.memoryref(getargs(args, frame)...)) + return Some{Any}(Core.memoryref(getargs(interp, args, frame)...)) end elseif @static (isdefinedglobal(Core, :set_binding_type!) && Core.set_binding_type! isa Core.Builtin) && f === Core.set_binding_type! if nargs == 2 - return Some{Any}(Core.set_binding_type!(lookup(frame, args[2]), lookup(frame, args[3]))) + return Some{Any}(Core.set_binding_type!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]))) elseif nargs == 3 - return Some{Any}(Core.set_binding_type!(lookup(frame, args[2]), lookup(frame, args[3]), lookup(frame, args[4]))) + return Some{Any}(Core.set_binding_type!(lookup(interp, frame, args[2]), lookup(interp, frame, args[3]), lookup(interp, frame, args[4]))) else - return Some{Any}(Core.set_binding_type!(getargs(args, frame)...)) + return Some{Any}(Core.set_binding_type!(getargs(interp, args, frame)...)) end elseif @static (isdefinedglobal(Core, :_apply_pure) && Core._apply_pure isa Core.Builtin) && f === Core._apply_pure - return Some{Any}(Core._apply_pure(getargs(args, frame)...)) + return Some{Any}(Core._apply_pure(getargs(interp, args, frame)...)) elseif @static (isdefinedglobal(Core, :_call_in_world) && Core._call_in_world isa Core.Builtin) && f === Core._call_in_world - return Some{Any}(Core._call_in_world(getargs(args, frame)...)) + return Some{Any}(Core._call_in_world(getargs(interp, args, frame)...)) elseif @static (isdefinedglobal(Core, :_call_latest) && Core._call_latest isa Core.Builtin) && f === Core._call_latest - args = getargs(args, frame) + args = getargs(interp, args, frame) if !expand return Some{Any}(Core._call_latest(args...)) end @@ -522,13 +522,13 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool) for x in args push!(new_expr.args, QuoteNode(x)) end - return maybe_recurse_expanded_builtin(frame, new_expr) + return maybe_recurse_expanded_builtin(interp, frame, new_expr) elseif f === Core.Intrinsics.llvmcall - return Some{Any}(Core.Intrinsics.llvmcall(getargs(args, frame)...)) + return Some{Any}(Core.Intrinsics.llvmcall(getargs(interp, args, frame)...)) end if isa(f, Core.IntrinsicFunction) - cargs = getargs(args, frame) + cargs = getargs(interp, args, frame) if f === Core.Intrinsics.have_fma && length(cargs) == 1 cargs1 = cargs[1] if cargs1 == Float64 @@ -556,7 +556,7 @@ function maybe_evaluate_builtin(frame, call_expr, expand::Bool) return Some{Any}(ccall(:jl_f_intrinsic_call, Any, (Any, Ptr{Any}, UInt32), f, cargs, length(cargs))) end if isa(f, typeof(kwinvoke)) - return Some{Any}(kwinvoke(getargs(args, frame)...)) + return Some{Any}(kwinvoke(getargs(interp, args, frame)...)) end return call_expr end diff --git a/src/commands.jl b/src/commands.jl index 6193d112..277cd029 100644 --- a/src/commands.jl +++ b/src/commands.jl @@ -1,50 +1,55 @@ """ - pc = finish!(recurse, frame, istoplevel=false) - pc = finish!(frame, istoplevel=false) + pc = finish!(interp::Interpreter, frame::Frame, istoplevel::Bool=false) + pc = finish!(frame::Frame, istoplevel::Bool=false) Run `frame` until execution terminates. `pc` is either `nothing` (if execution terminates when it hits a `return` statement) or a reference to a breakpoint. In the latter case, `leaf(frame)` returns the frame in which it hit the breakpoint. -`recurse` controls call evaluation; `recurse = Compiled()` evaluates :call expressions -by normal dispatch, whereas the default `recurse = finish_and_return!` uses recursive interpretation. +`interp` controls call evaluation; `interp = NonRecursiveInterpreter()` evaluates :call expressions +by normal dispatch, whereas the default `interp = RecursiveInterpreter()` uses recursive interpretation. """ -function finish!(@nospecialize(recurse), frame::Frame, istoplevel::Bool=false) +function finish!(interp::Interpreter, frame::Frame, istoplevel::Bool=false) while true - pc = step_expr!(recurse, frame, istoplevel) + pc = step_expr!(interp, frame, istoplevel) pc === nothing && return pc isa(pc, BreakpointRef) && return pc shouldbreak(frame, pc) && return BreakpointRef(frame.framecode, pc) end end -finish!(frame::Frame, istoplevel::Bool=false) = finish!(finish_and_return!, frame, istoplevel) +finish!(frame::Frame, istoplevel::Bool=false) = finish!(RecursiveInterpreter(), frame, istoplevel) """ - ret = finish_and_return!(recurse, frame, istoplevel::Bool=false) - ret = finish_and_return!(frame, istoplevel::Bool=false) + ret = finish_and_return!(interp::Interpreter, frame::Frame, istoplevel::Bool=false) + ret = finish_and_return!(frame::Frame, istoplevel::Bool=false) Call [`JuliaInterpreter.finish!`](@ref) and pass back the return value `ret`. If execution pauses at a breakpoint, `ret` is the reference to the breakpoint. """ -function finish_and_return!(@nospecialize(recurse), frame::Frame, istoplevel::Bool=false) - pc = finish!(recurse, frame, istoplevel) +function finish_and_return!(interp::Interpreter, frame::Frame, istoplevel::Bool=false) + pc = finish!(interp, frame, istoplevel) isa(pc, BreakpointRef) && return pc - return get_return(frame) + return get_return(interp, frame) end -finish_and_return!(frame::Frame, istoplevel::Bool=false) = finish_and_return!(finish_and_return!, frame, istoplevel) +finish_and_return!(frame::Frame, istoplevel::Bool=false) = finish_and_return!(RecursiveInterpreter(), frame, istoplevel) """ - bpref = dummy_breakpoint(recurse, frame::Frame, istoplevel) + BreakOnCall <: Interpreter -Return a fake breakpoint. `dummy_breakpoint` can be useful as the `recurse` argument to + bpref = @invoke evaluate_call!(BreakOnCall()::Interpreter, frame::Frame, istoplevel::Bool) + +An [`Interpreter`](@ref) returning a fake breakpoint. This can be useful as the `interp` argument to `evaluate_call!` (or any of the higher-order commands) to ensure that you return immediately after stepping into a call. """ -dummy_breakpoint(@nospecialize(recurse), frame::Frame, istoplevel) = BreakpointRef(frame.framecode, 0) +struct BreakOnCall <: Interpreter end +function finish_and_return!(::BreakOnCall, frame::Frame, ::Bool=false) + return BreakpointRef(frame.framecode, 0) +end """ - ret = finish_stack!(recurse, frame, rootistoplevel=false) - ret = finish_stack!(frame, rootistoplevel=false) + ret = finish_stack!(interp::Interpreter, frame::Frame, rootistoplevel::Bool=false) + ret = finish_stack!(frame::Frame, rootistoplevel::Bool=false) Unwind the callees of `frame`, finishing each before returning to the caller. `frame` itself is also finished. `rootistoplevel` should be true if the root frame is top-level. @@ -52,12 +57,12 @@ Unwind the callees of `frame`, finishing each before returning to the caller. `ret` is typically the returned value. If execution hits a breakpoint, `ret` will be a reference to the breakpoint. """ -function finish_stack!(@nospecialize(recurse), frame::Frame, rootistoplevel::Bool=false) +function finish_stack!(interp::Interpreter, frame::Frame, rootistoplevel::Bool=false) frame0 = frame frame = leaf(frame) while true istoplevel = rootistoplevel && frame.caller === nothing - ret = finish_and_return!(recurse, frame, istoplevel) + ret = finish_and_return!(interp, frame, istoplevel) isa(ret, BreakpointRef) && return ret frame === frame0 && return ret frame = return_from(frame) @@ -79,141 +84,144 @@ function finish_stack!(@nospecialize(recurse), frame::Frame, rootistoplevel::Boo shouldbreak(frame, pc) && return BreakpointRef(frame.framecode, pc) end end -finish_stack!(frame::Frame, istoplevel::Bool=false) = finish_stack!(finish_and_return!, frame, istoplevel) +finish_stack!(frame::Frame, istoplevel::Bool=false) = finish_stack!(RecursiveInterpreter(), frame, istoplevel) """ - pc = next_until!(predicate, recurse, frame, istoplevel=false) - pc = next_until!(predicate, frame, istoplevel=false) + pc = next_until!(predicate, interp::Interpreter, frame::Frame, istoplevel::Bool=false) + pc = next_until!(predicate, frame::Frame, istoplevel::Bool=false) Execute the current statement. Then step through statements of `frame` until the next statement satisfies `predicate(frame)`. `pc` will be the index of the statement at which evaluation terminates, `nothing` (if the frame reached a `return`), or a `BreakpointRef`. """ -function next_until!(@nospecialize(predicate), @nospecialize(recurse), frame::Frame, istoplevel::Bool=false) - pc = step_expr!(recurse, frame, istoplevel) +function next_until!(@nospecialize(predicate), interp::Interpreter, frame::Frame, istoplevel::Bool=false) + pc = step_expr!(interp, frame, istoplevel) while pc !== nothing && !isa(pc, BreakpointRef) shouldbreak(frame, pc) && return BreakpointRef(frame.framecode, pc) predicate(frame) && return pc - pc = step_expr!(recurse, frame, istoplevel) + pc = step_expr!(interp, frame, istoplevel) end return pc end -next_until!(predicate, frame::Frame, istoplevel::Bool=false) = - next_until!(predicate, finish_and_return!, frame, istoplevel) +next_until!(@nospecialize(predicate), frame::Frame, istoplevel::Bool=false) = + next_until!(predicate, RecursiveInterpreter(), frame, istoplevel) """ - pc = maybe_next_until!(predicate, recurse, frame, istoplevel=false) - pc = maybe_next_until!(predicate, frame, istoplevel=false) + pc = maybe_next_until!(predicate, interp::Interpreter, frame::Frame, istoplevel::Bool=false) + pc = maybe_next_until!(predicate, frame::Frame, istoplevel::Bool=false) Like [`next_until!`](@ref) except checks `predicate` before executing the current statment. """ -function maybe_next_until!(@nospecialize(predicate), @nospecialize(recurse), frame::Frame, istoplevel::Bool=false) +function maybe_next_until!(@nospecialize(predicate), interp::Interpreter, frame::Frame, istoplevel::Bool=false) predicate(frame) && return frame.pc - return next_until!(predicate, recurse, frame, istoplevel) + return next_until!(predicate, interp, frame, istoplevel) end maybe_next_until!(@nospecialize(predicate), frame::Frame, istoplevel::Bool=false) = - maybe_next_until!(predicate, finish_and_return!, frame, istoplevel) + maybe_next_until!(predicate, RecursiveInterpreter(), frame, istoplevel) """ - pc = next_call!(recurse, frame, istoplevel=false) - pc = next_call!(frame, istoplevel=false) + pc = next_call!(interp::Interpreter, frame::Frame, istoplevel::Bool=false) + pc = next_call!(frame::Frame, istoplevel::Bool=false) Execute the current statement. Continue stepping through `frame` until the next `ReturnNode` or `:call` expression. """ -next_call!(@nospecialize(recurse), frame::Frame, istoplevel::Bool=false) = - next_until!(frame -> is_call_or_return(pc_expr(frame)), recurse, frame, istoplevel) -next_call!(frame::Frame, istoplevel::Bool=false) = next_call!(finish_and_return!, frame, istoplevel) +next_call!(interp::Interpreter, frame::Frame, istoplevel::Bool=false) = + next_until!(frame::Frame -> is_call_or_return(pc_expr(frame)), interp, frame, istoplevel) +next_call!(frame::Frame, istoplevel::Bool=false) = next_call!(RecursiveInterpreter(), frame, istoplevel) """ - pc = maybe_next_call!(recurse, frame, istoplevel=false) - pc = maybe_next_call!(frame, istoplevel=false) + pc = maybe_next_call!(interp::Interpreter, frame::Frame, istoplevel::Bool=false) + pc = maybe_next_call!(frame::Frame, istoplevel::Bool=false) Return the current program counter of `frame` if it is a `ReturnNode` or `:call` expression. Otherwise, step through the statements of `frame` until the next `ReturnNode` or `:call` expression. """ -maybe_next_call!(@nospecialize(recurse), frame::Frame, istoplevel::Bool=false) = - maybe_next_until!(frame -> is_call_or_return(pc_expr(frame)), recurse, frame, istoplevel) -maybe_next_call!(frame::Frame, istoplevel::Bool=false) = maybe_next_call!(finish_and_return!, frame, istoplevel) +maybe_next_call!(interp::Interpreter, frame::Frame, istoplevel::Bool=false) = + maybe_next_until!(frame::Frame -> is_call_or_return(pc_expr(frame)), interp, frame, istoplevel) +maybe_next_call!(frame::Frame, istoplevel::Bool=false) = maybe_next_call!(RecursiveInterpreter(), frame, istoplevel) """ - pc = through_methoddef_or_done!(recurse, frame) - pc = through_methoddef_or_done!(frame) + pc = through_methoddef_or_done!(interp::Interpreter, frame::Frame) + pc = through_methoddef_or_done!(frame::Frame) Runs `frame` at top level until it either finishes (e.g., hits a `return` statement) or defines a new method. """ -function through_methoddef_or_done!(@nospecialize(recurse), frame::Frame) - predicate(frame) = (stmt = pc_expr(frame); isexpr(stmt, :method, 3) || isexpr(stmt, :thunk)) - pc = next_until!(predicate, recurse, frame, true) +function through_methoddef_or_done!(interp::Interpreter, frame::Frame) + pc = next_until!(interp, frame, true) do frame::Frame + stmt = pc_expr(frame) + return isexpr(stmt, :method, 3) || isexpr(stmt, :thunk) + end (pc === nothing || isa(pc, BreakpointRef)) && return pc - return step_expr!(recurse, frame, true) # define the method and return + return step_expr!(interp, frame, true) # define the method and return end -through_methoddef_or_done!(@nospecialize(recurse), t::Tuple{Module,Expr,Frame}) = - through_methoddef_or_done!(recurse, t[end]) -through_methoddef_or_done!(@nospecialize(recurse), modex::Tuple{Module,Expr,Expr}) = Core.eval(modex[1], modex[3]) -through_methoddef_or_done!(@nospecialize(recurse), ::Nothing) = nothing -through_methoddef_or_done!(arg) = through_methoddef_or_done!(finish_and_return!, arg) +through_methoddef_or_done!(interp::Interpreter, t::Tuple{Module,Expr,Frame}) = + through_methoddef_or_done!(interp, t[end]) +through_methoddef_or_done!(::Interpreter, modex::Tuple{Module,Expr,Expr}) = Core.eval(modex[1], modex[3]) +through_methoddef_or_done!(::Interpreter, ::Nothing) = nothing +through_methoddef_or_done!(@nospecialize arg) = through_methoddef_or_done!(RecursiveInterpreter(), arg) # Sentinel to see if the call was a wrapper call struct Wrapper end """ - pc = next_line!(recurse, frame, istoplevel=false) - pc = next_line!(frame, istoplevel=false) + pc = next_line!(interp::Interpreter, frame::Frame, istoplevel::Bool=false) + pc = next_line!(frame::Frame, istoplevel::Bool=false) Execute until reaching the first call of the next line of the source code. Upon return, `pc` is either the new program counter, `nothing` if a `return` is reached, or a `BreakpointRef` if it encountered a wrapper call. In the latter case, call `leaf(frame)` to obtain the new execution frame. """ -function next_line!(@nospecialize(recurse), frame::Frame, istoplevel::Bool=false) +function next_line!(interp::Interpreter, frame::Frame, istoplevel::Bool=false) pc = frame.pc initialline, initialfile = linenumber(frame, pc), getfile(frame, pc) if initialline === nothing || initialfile === nothing - return step_expr!(recurse, frame, istoplevel) + return step_expr!(interp, frame, istoplevel) end - return _next_line!(recurse, frame, istoplevel, initialline, initialfile) # avoid boxing + return _next_line!(interp, frame, istoplevel, initialline, initialfile) # avoid boxing end -function _next_line!(@nospecialize(recurse), frame::Frame, istoplevel, initialline::Int, initialfile::String) - predicate(frame) = is_return(pc_expr(frame)) || (linenumber(frame) != initialline || getfile(frame) != initialfile) - - pc = next_until!(predicate, recurse, frame, istoplevel) +function _next_line!(interp::Interpreter, frame::Frame, istoplevel, initialline::Int, initialfile::String) + pc = next_until!(interp, frame, istoplevel) do frame::Frame + return is_return(pc_expr(frame)) || (linenumber(frame) != initialline || getfile(frame) != initialfile) + end (pc === nothing || isa(pc, BreakpointRef)) && return pc - maybe_step_through_kwprep!(recurse, frame, istoplevel) - maybe_next_call!(recurse, frame, istoplevel) + maybe_step_through_kwprep!(interp, frame, istoplevel) + maybe_next_call!(interp, frame, istoplevel) end -next_line!(frame::Frame, istoplevel::Bool=false) = next_line!(finish_and_return!, frame, istoplevel) +next_line!(frame::Frame, istoplevel::Bool=false) = next_line!(RecursiveInterpreter(), frame, istoplevel) """ - pc = until_line!(recurse, frame, line=nothing istoplevel=false) + pc = until_line!(interp::Interpreter, frame, line=nothing istoplevel=false) pc = until_line!(frame, line=nothing, istoplevel=false) Execute until the current frame reaches a line greater than `line`. If `line == nothing` execute until the current frame reaches any line greater than the current line. """ -function until_line!(@nospecialize(recurse), frame::Frame, line::Union{Nothing, Integer}=nothing, istoplevel::Bool=false) +function until_line!(interp::Interpreter, frame::Frame, line::Union{Nothing, Integer}=nothing, istoplevel::Bool=false) pc = frame.pc initialline, initialfile = linenumber(frame, pc), getfile(frame, pc) line === nothing && (line = initialline + 1) - predicate(frame) = is_return(pc_expr(frame)) || (linenumber(frame) >= line && getfile(frame) == initialfile) - pc = next_until!(predicate, frame, istoplevel) + pc = next_until!(interp, frame, istoplevel) do frame::Frame + return is_return(pc_expr(frame)) || (linenumber(frame) >= line && getfile(frame) == initialfile) + end (pc === nothing || isa(pc, BreakpointRef)) && return pc - maybe_step_through_kwprep!(recurse, frame, istoplevel) - maybe_next_call!(recurse, frame, istoplevel) + maybe_step_through_kwprep!(interp, frame, istoplevel) + maybe_next_call!(interp, frame, istoplevel) end -until_line!(frame::Frame, line::Union{Nothing, Integer}=nothing, istoplevel::Bool=false) = until_line!(finish_and_return!, frame, line, istoplevel) +until_line!(frame::Frame, line::Union{Nothing, Integer}=nothing, istoplevel::Bool=false) = until_line!(RecursiveInterpreter(), frame, line, istoplevel) """ - cframe = maybe_step_through_wrapper!(recurse, frame) - cframe = maybe_step_through_wrapper!(frame) + cframe = maybe_step_through_wrapper!(interp::Interpreter, frame::Frame) + cframe = maybe_step_through_wrapper!(frame::Frame) Return the new frame of execution, potentially stepping through "wrapper" methods like those that supply default positional arguments or handle keywords. `cframe` is the leaf frame from which execution should start. """ -function maybe_step_through_wrapper!(@nospecialize(recurse), frame::Frame) +function maybe_step_through_wrapper!(interp::Interpreter, frame::Frame) code = frame.framecode src = code.src stmts, scope = src.code, code.scope::Method @@ -238,35 +246,35 @@ function maybe_step_through_wrapper!(@nospecialize(recurse), frame::Frame) # If the last expr calls #self# or passes it to an implementation method, # this is a wrapper function that we might want to step through while frame.pc != length(stmts)-1 - pc = next_call!(recurse, frame, false) # since we're in a Method we're not at toplevel + pc = next_call!(interp, frame, false) # since we're in a Method we're not at toplevel if pc === nothing || isa(pc, BreakpointRef) return frame end end - ret = evaluate_call!(dummy_breakpoint, frame, last) - if !isa(ret, BreakpointRef) # Happens if next call is Compiled + ret = @invoke evaluate_call!(BreakOnCall()::Interpreter, frame::Frame, last::Expr) + if !isa(ret, BreakpointRef) # Happens if next call is compiled return frame end frame.framedata.ssavalues[frame.pc] = Wrapper() - return maybe_step_through_wrapper!(recurse, callee(frame)) + return maybe_step_through_wrapper!(interp, callee(frame)) end maybe_step_through_nkw_meta!(frame) return frame end -maybe_step_through_wrapper!(frame::Frame) = maybe_step_through_wrapper!(finish_and_return!, frame) +maybe_step_through_wrapper!(frame::Frame) = maybe_step_through_wrapper!(RecursiveInterpreter(), frame) const kwhandler = Core.kwcall const kwextrastep = 0 const kw_has_f_first = VERSION.major == 1 && VERSION.minor == 11 """ - frame = maybe_step_through_kwprep!(recurse, frame) - frame = maybe_step_through_kwprep!(frame) + frame = maybe_step_through_kwprep!(interp::Interpreter, frame::Frame) + frame = maybe_step_through_kwprep!(frame::Frame) If `frame.pc` points to the beginning of preparatory work for calling a keyword-argument function, advance forward until the actual call. """ -function maybe_step_through_kwprep!(@nospecialize(recurse), frame::Frame, istoplevel::Bool=false) +function maybe_step_through_kwprep!(interp::Interpreter, frame::Frame, istoplevel::Bool=false) # XXX This code just does pattern-matching based on the current state of the compiler # internals, which means this is very fragile against any future changes to those # internals. We really need a more general and robust solution, but achieving that @@ -279,7 +287,7 @@ function maybe_step_through_kwprep!(@nospecialize(recurse), frame::Frame, istopl pc += 1 stmt = pc_expr(frame, pc) elseif kw_has_f_first && pc < n && is_empty_namedtuple(pc_expr(frame, pc+1)) && isa(stmt, QuoteNode) - pc = step_expr!(recurse, frame, istoplevel) + pc = step_expr!(interp, frame, istoplevel) stmt = pc_expr(frame, pc) end if isa(stmt, Tuple{Symbol,Vararg{Symbol}}) @@ -294,16 +302,16 @@ function maybe_step_through_kwprep!(@nospecialize(recurse), frame::Frame, istopl stmt4, stmt5 = src.code[pc+4+isbindingresolved_deprecated], src.code[pc+5+isbindingresolved_deprecated] if isexpr(stmt4, :call) && (is_quotenode_egal(stmt4.args[1], kwhandler) || is_global_ref(stmt4.args[1], Core, :kwcall)) while pc < pccall - pc = step_expr!(recurse, frame, istoplevel) + pc = step_expr!(interp, frame, istoplevel) end return frame elseif isexpr(stmt5, :call) && (is_quotenode_egal(stmt5.args[1], kwhandler) || is_global_ref(stmt5.args[1], Core, :kwcall)) && pccall+1 <= n # This happens when the call is scoped by a module pccall += 1 while pc < pccall - pc = step_expr!(recurse, frame, istoplevel) + pc = step_expr!(interp, frame, istoplevel) end - maybe_next_call!(recurse, frame, istoplevel) + maybe_next_call!(interp, frame, istoplevel) return frame end end @@ -321,7 +329,7 @@ function maybe_step_through_kwprep!(@nospecialize(recurse), frame::Frame, istopl issplatcall, callee = unpack_splatcall(src.code[pcsplat], src) if issplatcall && is_bodyfunc(callee) while pc < pcsplat - pc = step_expr!(recurse, frame, istoplevel) + pc = step_expr!(interp, frame, istoplevel) end return frame end @@ -332,7 +340,7 @@ function maybe_step_through_kwprep!(@nospecialize(recurse), frame::Frame, istopl if isa(stmt2, Expr) if stmt2.head === :call && length(stmt2.args) >= 3 && stmt2.args[2] === SSAValue(pc+1) && stmt2.args[3] === SlotNumber(1) while pc < pccall - pc = step_expr!(recurse, frame, istoplevel) + pc = step_expr!(interp, frame, istoplevel) end end end @@ -341,11 +349,11 @@ function maybe_step_through_kwprep!(@nospecialize(recurse), frame::Frame, istopl stmtk = src.code[pccall-1] if isexpr(stmtk, :call) && (is_quotenode_egal(stmtk.args[1], kwhandler) || is_global_ref(stmtk.args[1], Core, :kwcall)) for i = 1:4 + isbindingresolved_deprecated - pc = step_expr!(recurse, frame, istoplevel) + pc = step_expr!(interp, frame, istoplevel) end stmti = src.code[pc] if isexpr(stmti, :call) && (is_quotenode_egal(stmti.args[1], #= deliberately not kwhandler =# Core.kwfunc) || is_global_ref(stmti.args[1], Core, :kwfunc)) - pc = step_expr!(recurse, frame, istoplevel) + pc = step_expr!(interp, frame, istoplevel) end end end @@ -355,7 +363,7 @@ function maybe_step_through_kwprep!(@nospecialize(recurse), frame::Frame, istopl return frame end maybe_step_through_kwprep!(frame::Frame, istoplevel::Bool=false) = - maybe_step_through_kwprep!(finish_and_return!, frame, istoplevel) + maybe_step_through_kwprep!(RecursiveInterpreter(), frame, istoplevel) @static if isbindingresolved_deprecated function is_empty_namedtuple(stmt) @@ -369,7 +377,7 @@ else end """ - ret = maybe_reset_frame!(recurse, frame, pc, rootistoplevel) + ret = maybe_reset_frame!(interp::Interpreter, frame::Frame, pc, rootistoplevel::Bool) Perform a return to the caller, or descend to the level of a breakpoint. `pc` is the return state from the previous command (e.g., `next_call!` or similar). @@ -382,27 +390,27 @@ Perform a return to the caller, or descend to the level of a breakpoint. where `cframe` is the frame from which execution should continue and `cpc` is the state of `cframe` (the program counter, a `BreakpointRef`, or `nothing`). """ -function maybe_reset_frame!(@nospecialize(recurse), frame::Frame, @nospecialize(pc), rootistoplevel::Bool) +function maybe_reset_frame!(interp::Interpreter, frame::Frame, @nospecialize(pc), rootistoplevel::Bool) isa(pc, BreakpointRef) && return leaf(frame), pc if pc === nothing - val = get_return(frame) + val = get_return(interp, frame) frame = return_from(frame) frame === nothing && return nothing ssavals = frame.framedata.ssavalues is_wrapper = isassigned(ssavals, frame.pc) && ssavals[frame.pc] === Wrapper() maybe_assign!(frame, val) - frame.pc >= nstatements(frame.framecode) && return maybe_reset_frame!(recurse, frame, nothing, rootistoplevel) + frame.pc >= nstatements(frame.framecode) && return maybe_reset_frame!(interp, frame, nothing, rootistoplevel) frame.pc += 1 if is_wrapper - return maybe_reset_frame!(recurse, frame, finish!(recurse, frame), rootistoplevel) + return maybe_reset_frame!(interp, frame, finish!(interp, frame), rootistoplevel) end - pc = maybe_next_call!(recurse, frame, rootistoplevel && frame.caller===nothing) - return maybe_reset_frame!(recurse, frame, pc, rootistoplevel) + pc = maybe_next_call!(interp, frame, rootistoplevel && frame.caller===nothing) + return maybe_reset_frame!(interp, frame, pc, rootistoplevel) end return frame, pc end maybe_reset_frame!(frame::Frame, @nospecialize(pc), rootistoplevel::Bool) = - maybe_reset_frame!(finish_and_return!, frame, pc, rootistoplevel) + maybe_reset_frame!(RecursiveInterpreter(), frame, pc, rootistoplevel) # Unwind the stack until an exc is eventually caught, thereby # returning the frame that caught the exception at the pc of the catch @@ -421,7 +429,7 @@ function unwind_exception(frame::Frame, @nospecialize(exc)) rethrow(exc) end -function maybe_step_through_nkw_meta!(frame) +function maybe_step_through_nkw_meta!(frame::Frame) stmt = pc_expr(frame) if stmt === nothing || (isexpr(stmt, :meta) && (stmt::Expr).args[1] === :nkw) @assert frame.pc == 1 @@ -429,8 +437,7 @@ function maybe_step_through_nkw_meta!(frame) end end - -function more_calls_on_current_line(frame) +function more_calls_on_current_line(frame::Frame) _, curr_line = whereis(frame) curr_pc = frame.pc + 1 while curr_pc <= length(frame.framecode.src.code) @@ -443,8 +450,9 @@ function more_calls_on_current_line(frame) end """ - ret = debug_command(recurse, frame, cmd, rootistoplevel=false; line=nothing) - ret = debug_command(frame, cmd, rootistoplevel=false; line=nothing) + ret = debug_command(interp::Interpreter, frame::Frame, cmd::Symbol, rootistoplevel::Bool=false; + line::Union{Nothing,Integer}=nothing) + ret = debug_command(frame::Frame, cmd::Symbol, rootistoplevel::Bool=false; line=nothing) Perform one "debugger" command. The keyword arguments are not used for all debug commands. `cmd` should be one of: @@ -466,12 +474,13 @@ or one of the 'advanced' commands `rootistoplevel` and `ret` are as described for [`JuliaInterpreter.maybe_reset_frame!`](@ref). """ -function debug_command(@nospecialize(recurse), frame::Frame, cmd::Symbol, rootistoplevel::Bool=false; line=nothing) - function nicereturn!(@nospecialize(recurse), frame::Frame, pc, rootistoplevel) +function debug_command(interp::Interpreter, frame::Frame, cmd::Symbol, rootistoplevel::Bool=false; + line::Union{Nothing,Integer}=nothing) + function nicereturn!(interp::Interpreter, frame::Frame, @nospecialize(pc), rootistoplevel::Bool) if pc === nothing || isa(pc, BreakpointRef) - return maybe_reset_frame!(recurse, frame, pc, rootistoplevel) + return maybe_reset_frame!(interp, frame, pc, rootistoplevel) end - maybe_step_through_kwprep!(recurse, frame, rootistoplevel && frame.caller === nothing) + maybe_step_through_kwprep!(interp, frame, rootistoplevel && frame.caller === nothing) return frame, frame.pc end @@ -484,18 +493,18 @@ function debug_command(@nospecialize(recurse), frame::Frame, cmd::Symbol, rootis is_si = true end try - cmd === :nc && return nicereturn!(recurse, frame, next_call!(recurse, frame, istoplevel), rootistoplevel) - cmd === :n && return maybe_reset_frame!(recurse, frame, next_line!(recurse, frame, istoplevel), rootistoplevel) - cmd === :se && return maybe_reset_frame!(recurse, frame, step_expr!(recurse, frame, istoplevel), rootistoplevel) - cmd === :until && return maybe_reset_frame!(recurse, frame, until_line!(recurse, frame, line, istoplevel), rootistoplevel) + cmd === :nc && return nicereturn!(interp, frame, next_call!(interp, frame, istoplevel), rootistoplevel) + cmd === :n && return maybe_reset_frame!(interp, frame, next_line!(interp, frame, istoplevel), rootistoplevel) + cmd === :se && return maybe_reset_frame!(interp, frame, step_expr!(interp, frame, istoplevel), rootistoplevel) + cmd === :until && return maybe_reset_frame!(interp, frame, until_line!(interp, frame, line, istoplevel), rootistoplevel) if cmd === :sl while more_calls_on_current_line(frame) - next_call!(recurse, frame, istoplevel) + next_call!(interp, frame, istoplevel) end - return debug_command(recurse, frame, :s, rootistoplevel; line) + return debug_command(interp, frame, :s, rootistoplevel; line) end if cmd === :sr - maybe_next_until!(frame -> is_return(pc_expr(frame)), recurse, frame, istoplevel) + maybe_next_until!(frame::Frame -> is_return(pc_expr(frame)), interp, frame, istoplevel) return frame, frame.pc end enter_generated = false @@ -504,27 +513,27 @@ function debug_command(@nospecialize(recurse), frame::Frame, cmd::Symbol, rootis cmd = :s end if cmd === :s - pc = maybe_next_call!(recurse, frame, istoplevel) - (isa(pc, BreakpointRef) || pc === nothing) && return maybe_reset_frame!(recurse, frame, pc, rootistoplevel) - is_si || maybe_step_through_kwprep!(recurse, frame, istoplevel) + pc = maybe_next_call!(interp, frame, istoplevel) + (isa(pc, BreakpointRef) || pc === nothing) && return maybe_reset_frame!(interp, frame, pc, rootistoplevel) + is_si || maybe_step_through_kwprep!(interp, frame, istoplevel) pc = frame.pc stmt0 = stmt = pc_expr(frame, pc) - is_return(stmt0) && return maybe_reset_frame!(recurse, frame, nothing, rootistoplevel) + is_return(stmt0) && return maybe_reset_frame!(interp, frame, nothing, rootistoplevel) if isexpr(stmt, :(=)) stmt = stmt.args[2] end local ret try - ret = evaluate_call!(dummy_breakpoint, frame, stmt; enter_generated=enter_generated) + ret = @invoke evaluate_call!(BreakOnCall()::Interpreter, frame::Frame, stmt::Expr, enter_generated::Bool) catch err - ret = handle_err(recurse, frame, err) + ret = handle_err(interp, frame, err) return isa(ret, BreakpointRef) ? (leaf(frame), ret) : ret end if isa(ret, BreakpointRef) newframe = leaf(frame) cmd0 === :si && return newframe, ret - is_si || (newframe = maybe_step_through_wrapper!(recurse, newframe)) - is_si || maybe_step_through_kwprep!(recurse, newframe, istoplevel) + is_si || (newframe = maybe_step_through_wrapper!(interp, newframe)) + is_si || maybe_step_through_kwprep!(interp, newframe, istoplevel) return newframe, BreakpointRef(newframe.framecode, 0) end # if we got here, the call returned a value @@ -534,19 +543,19 @@ function debug_command(@nospecialize(recurse), frame::Frame, cmd::Symbol, rootis end if cmd === :c r = root(frame) - ret = finish_stack!(recurse, r, rootistoplevel) + ret = finish_stack!(interp, r, rootistoplevel) return isa(ret, BreakpointRef) ? (leaf(r), ret) : nothing end - cmd === :finish && return maybe_reset_frame!(recurse, frame, finish!(recurse, frame, istoplevel), rootistoplevel) + cmd === :finish && return maybe_reset_frame!(interp, frame, finish!(interp, frame, istoplevel), rootistoplevel) catch err frame = unwind_exception(frame, err) if cmd === :c - return debug_command(recurse, frame, :c, istoplevel) + return debug_command(interp, frame, :c, istoplevel) else - return debug_command(recurse, frame, :nc, istoplevel) + return debug_command(interp, frame, :nc, istoplevel) end end throw(ArgumentError("command $cmd not recognized")) end debug_command(frame::Frame, cmd::Symbol, rootistoplevel::Bool=false; kwargs...) = - debug_command(finish_and_return!, frame, cmd, rootistoplevel; kwargs...) + debug_command(RecursiveInterpreter(), frame, cmd, rootistoplevel; kwargs...) diff --git a/src/construct.jl b/src/construct.jl index dbf72755..5896c1c9 100644 --- a/src/construct.jl +++ b/src/construct.jl @@ -15,20 +15,20 @@ for the generator itself, its framecode would be stored in [`framedict`](@ref). const genframedict = Dict{Tuple{Method,Type},FrameCode}() # the same for @generated functions """ -`meth ∈ compiled_methods` indicates that `meth` should be run using [`Compiled`](@ref) +`meth ∈ compiled_methods` indicates that `meth` should be run using [`NonRecursiveInterpreter`](@ref) rather than recursed into via the interpreter. """ const compiled_methods = Set{Method}() """ -`meth ∈ interpreted_methods` indicates that `meth` should *not* be run using [`Compiled`](@ref) +`meth ∈ interpreted_methods` indicates that `meth` should *not* be run using [`NonRecursiveInterpreter`](@ref) and recursed into via the interpreter. This takes precedence over [`compiled_methods`](@ref) and [`compiled_modules`](@ref). """ const interpreted_methods = Set{Method}() """ -`mod ∈ compiled_modules` indicates that any method in `mod` should be run using [`Compiled`](@ref) +`mod ∈ compiled_modules` indicates that any method in `mod` should be run using [`NonRecursiveInterpreter`](@ref) rather than recursed into via the interpreter. """ const compiled_modules = Set{Module}() @@ -691,7 +691,9 @@ end # call expression for further processing. function extract_args(__module__, ex0) if isa(ex0, Expr) - if any(a->(isexpr(a, :kw) || isexpr(a, :parameters)), ex0.args) + if isexpr(ex0, :macrocall) # Make @edit @time 1+2 edit the macro by using the types of the *expressions* + return error("Macros are not supported in @interpret") + elseif any(@nospecialize(a)->(isexpr(a, :kw) || isexpr(a, :parameters)), ex0.args) arg1, args, kwargs = gensym("arg1"), gensym("args"), gensym("kwargs") return quote $arg1 = $(ex0.args[1]) @@ -707,9 +709,6 @@ function extract_args(__module__, ex0) mapany(x->isexpr(x,:parameters) ? QuoteNode(x) : x, ex0.args)...) end end - if isexpr(ex0, :macrocall) # Make @edit @time 1+2 edit the macro by using the types of the *expressions* - return error("Macros are not supported in @enter") - end ex = Meta.lower(__module__, ex0) if !isa(ex, Expr) return error("expression is not a function call or symbol") @@ -727,30 +726,13 @@ function extract_args(__module__, ex0) end end return error("expression is not a function call, " - * "or is too complex for @enter to analyze; " + * "or is too complex for @interpret to analyze; " * "break it down to simpler parts if possible") end -""" - @interpret f(args; kwargs...) - -Evaluate `f` on the specified arguments using the interpreter. - -# Example - -```jldoctest -julia> a = [1, 7]; - -julia> sum(a) -8 - -julia> @interpret sum(a) -8 -``` -""" -macro interpret(arg) +function interpret(mod::Module, @nospecialize(ex0); interp=RecursiveInterpreter()) args = try - extract_args(__module__, arg) + extract_args(mod, ex0) catch e return :(throw($e)) end @@ -762,7 +744,7 @@ macro interpret(arg) elseif shouldbreak(frame, 1) frame, BreakpointRef(frame.framecode, 1) else - local ret = finish_and_return!(frame) + local ret = finish_and_return!($interp, frame) # We deliberately return the top frame here; future debugging commands # via debug_command may alter the leaves, we want the top frame so we can # ultimately do `get_return`. @@ -770,3 +752,38 @@ macro interpret(arg) end end end + +function interpret(mod::Module, opt, xs...; kwargs...) + if isexpr(opt, :(=)) + optname, optval = opt.args + optname isa Symbol || error("Invalid @interpret call: $optname is not a symbol") + if optname === :interp + return interpret(mod, xs...; interp=esc(optval), kwargs...) + else + error("Invalid @interpret call: $optname is not a recognized option") + end + else + error("Invalid @interpret call: $opt is not a keyword") + end +end + +""" + @interpret [interp] f(args; kwargs...) + +Evaluate `f` on the specified arguments using the interpreter. + +# Example + +```jldoctest +julia> a = [1, 7]; + +julia> sum(a) +8 + +julia> @interpret sum(a) +8 +``` +""" +macro interpret(ex0, exs...) + return interpret(__module__, ex0, exs...) +end diff --git a/src/interpret.jl b/src/interpret.jl index c552bd4e..e53d2653 100644 --- a/src/interpret.jl +++ b/src/interpret.jl @@ -10,24 +10,25 @@ function lookup_var(frame::Frame, slot::SlotNumber) end """ - lookup(frame::Frame, node) + lookup([interp::Interpreter=RecursiveInterpreter()], frame::Frame, node) Looks up previously-computed values referenced as `SSAValue`, `SlotNumber`, `GlobalRef`, sparam or exception reference expression. It will also lookup `Symbol`s as global reference in the context of `moduleof(frame)::Module`. If none of the above apply, the value of `node` will be returned. """ -function lookup(frame::Frame, @nospecialize(node)) +function lookup(interp::Interpreter, frame::Frame, @nospecialize(node)) + if isa(node, Symbol) + node = GlobalRef(moduleof(frame), node) + end if isa(node, SSAValue) return lookup_var(frame, node) - elseif isa(node, GlobalRef) - return lookup_var(frame, node) elseif isa(node, SlotNumber) return lookup_var(frame, node) - elseif isa(node, Symbol) - return @invokelatest getglobal(moduleof(frame), node) + elseif isa(node, GlobalRef) + return lookup_var(frame, node) elseif isa(node, Expr) - return lookup_expr(frame, node) + return lookup_expr(interp, frame, node) else # fallback if isa(node, QuoteNode) return node.value @@ -35,6 +36,7 @@ function lookup(frame::Frame, @nospecialize(node)) return node end end +lookup(frame::Frame, @nospecialize(node)) = lookup(RecursiveInterpreter(), frame, node) macro lookup(frame, node) f, l = __source__.file, __source__.line @@ -47,7 +49,7 @@ macro lookup(_, frame, node) return :(lookup($(esc(frame)), $(esc(node)))) end -function lookup_expr(frame::Frame, e::Expr) +function lookup_expr(interp::Interpreter, frame::Frame, e::Expr) head = e.head head === :the_exception && return frame.framedata.last_exception[] if head === :static_parameter @@ -61,13 +63,13 @@ function lookup_expr(frame::Frame, e::Expr) end head === :boundscheck && length(e.args) == 0 && return true if head === :call - f = lookup(frame, e.args[1]) + f = lookup(interp, frame, e.args[1]) if (@static VERSION < v"1.11.0-DEV.1180" && true) && f === Core.svec # work around for a linearization bug in Julia (https://github.com/JuliaLang/julia/pull/52497) - return f(Any[lookup(frame, e.args[i]) for i in 2:length(e.args)]...) + return Core.svec(Any[lookup(interp, frame, e.args[i]) for i in 2:length(e.args)]...) elseif f === Core.tuple # handling for ccall literal syntax - return f(Any[lookup(frame, e.args[i]) for i in 2:length(e.args)]...) + return Core.tuple(Any[lookup(interp, frame, e.args[i]) for i in 2:length(e.args)]...) end end error("invalid lookup expr ", e) @@ -78,21 +80,11 @@ end # and hence our re-use of the `callargs` field of Frame would introduce # bugs. Since these nodes use a very limited repertoire of calls, we can special-case # this quite easily. -function lookup_or_eval(@nospecialize(recurse), frame::Frame, @nospecialize(node)) - if isa(node, SSAValue) - return lookup_var(frame, node) - elseif isa(node, SlotNumber) - return lookup_var(frame, node) - elseif isa(node, GlobalRef) - return lookup_var(frame, node) - elseif isa(node, Symbol) - return invokelatest(getfield, moduleof(frame), node) - elseif isa(node, QuoteNode) - return node.value - elseif isa(node, Expr) +function lookup_nested(interp::Interpreter, frame::Frame, @nospecialize(node)) + if isa(node, Expr) ex = Expr(node.head) for arg in node.args - push!(ex.args, lookup_or_eval(recurse, frame, arg)) + push!(ex.args, lookup_nested(interp, frame, arg)) end if ex.head === :call f = ex.args[1] @@ -119,14 +111,10 @@ function lookup_or_eval(@nospecialize(recurse), frame::Frame, @nospecialize(node @invokelatest error("unknown call f introduced by ccall lowering ", f) end else - return lookup_expr(frame, ex) + return lookup_expr(interp, frame, ex) end - elseif isa(node, Int) || isa(node, Number) # Number is slow, requires subtyping - return node - elseif isa(node, Type) - return node end - return eval_rhs(recurse, frame, node) + return lookup(interp, frame, node) end function resolvefc(frame::Frame, @nospecialize(expr)) @@ -149,28 +137,28 @@ function resolvefc(frame::Frame, @nospecialize(expr)) @invokelatest error("unexpected ccall to ", expr) end -function collect_args(@nospecialize(recurse), frame::Frame, call_expr::Expr; isfc::Bool=false) +function collect_args(interp::Interpreter, frame::Frame, call_expr::Expr; isfc::Bool=false) args = frame.framedata.callargs resize!(args, length(call_expr.args)) - args[1] = isfc ? resolvefc(frame, call_expr.args[1]) : lookup(frame, call_expr.args[1]) + args[1] = isfc ? resolvefc(frame, call_expr.args[1]) : lookup(interp, frame, call_expr.args[1]) for i = 2:length(args) if isexpr(call_expr.args[i], :call) - args[i] = lookup_or_eval(recurse, frame, call_expr.args[i]) + args[i] = lookup_nested(interp, frame, call_expr.args[i]::Expr) else - args[i] = lookup(frame, call_expr.args[i]) + args[i] = lookup(interp, frame, call_expr.args[i]) end end return args end """ - ret = evaluate_foreigncall(recurse, frame::Frame, call_expr) + ret = evaluate_foreigncall(interp, frame::Frame, call_expr) Evaluate a `:foreigncall` (from a `ccall`) statement `callexpr` in the context of `frame`. """ -function evaluate_foreigncall(@nospecialize(recurse), frame::Frame, call_expr::Expr) +function evaluate_foreigncall(interp::Interpreter, frame::Frame, call_expr::Expr) head = call_expr.head - args = collect_args(recurse, frame, call_expr; isfc = head === :foreigncall) + args = collect_args(interp, frame, call_expr; isfc = head === :foreigncall) for i = 2:length(args) arg = args[i] args[i] = isa(arg, Symbol) ? QuoteNode(arg) : arg @@ -200,11 +188,11 @@ function evaluate_foreigncall(@nospecialize(recurse), frame::Frame, call_expr::E end # We have to intercept ccalls / llvmcalls before we try it as a builtin -function bypass_builtins(@nospecialize(recurse), frame::Frame, call_expr::Expr, pc::Int) +function bypass_builtins(interp::Interpreter, frame::Frame, call_expr::Expr, pc::Int) if isassigned(frame.framecode.methodtables, pc) tme = frame.framecode.methodtables[pc] if isa(tme, Compiled) - fargs = collect_args(recurse, frame, call_expr) + fargs = collect_args(interp, frame, call_expr) f = to_function(fargs[1]) fmod = parentmodule(f)::Module if fmod === JuliaInterpreter.CompiledCalls || fmod === Core.Compiler @@ -219,39 +207,42 @@ function bypass_builtins(@nospecialize(recurse), frame::Frame, call_expr::Expr, end function native_call(fargs::Vector{Any}, frame::Frame) - f = popfirst!(fargs) # now it's really just `args` - if (@static isdefinedglobal(Core.IR, :EnterNode) && true) - newscope = Core.current_scope() - if newscope !== nothing || !isempty(frame.framedata.current_scopes) - for scope in frame.framedata.current_scopes - newscope = Scope(newscope, scope.values...) - end - ex = Expr(:tryfinally, :($f($fargs...)), nothing, newscope) - return Core.eval(moduleof(frame), ex) + f = popfirst!(fargs) + @something maybe_eval_with_scope(f, fargs, frame) return @invokelatest f(fargs...) +end + +function maybe_eval_with_scope(@nospecialize(f), fargs::Vector{Any}, frame::Frame) + @static isdefinedglobal(Core.IR, :EnterNode) || return nothing + newscope = Core.current_scope() + if newscope !== nothing || !isempty(frame.framedata.current_scopes) + for scope in frame.framedata.current_scopes + newscope = Scope(newscope, scope.values...) end + ex = Expr(:tryfinally, :($f($fargs...)), nothing, newscope) + return Some{Any}(Core.eval(moduleof(frame), ex)) end - return @invokelatest f(fargs...) + return nothing end -function evaluate_call_compiled!(::Compiled, frame::Frame, call_expr::Expr; enter_generated::Bool=false) +function evaluate_call!(interp::NonRecursiveInterpreter, frame::Frame, call_expr::Expr, enter_generated::Bool=false) # @assert !enter_generated pc = frame.pc - ret = bypass_builtins(Compiled(), frame, call_expr, pc) + ret = bypass_builtins(interp, frame, call_expr, pc) isa(ret, Some{Any}) && return ret.value - ret = maybe_evaluate_builtin(frame, call_expr, false) + ret = maybe_evaluate_builtin(interp, frame, call_expr, false) isa(ret, Some{Any}) && return ret.value - fargs = collect_args(Compiled(), frame, call_expr) + fargs = collect_args(interp, frame, call_expr) return native_call(fargs, frame) end -function evaluate_call_recurse!(@nospecialize(recurse), frame::Frame, call_expr::Expr; enter_generated::Bool=false) +function evaluate_call!(interp::Interpreter, frame::Frame, call_expr::Expr, enter_generated::Bool=false) pc = frame.pc - ret = bypass_builtins(recurse, frame, call_expr, pc) + ret = bypass_builtins(interp, frame, call_expr, pc) isa(ret, Some{Any}) && return ret.value - ret = maybe_evaluate_builtin(frame, call_expr, true) + ret = maybe_evaluate_builtin(interp, frame, call_expr, true) isa(ret, Some{Any}) && return ret.value call_expr = ret - fargs = collect_args(recurse, frame, call_expr) + fargs = collect_args(interp, frame, call_expr) if fargs[1] === Core.eval return Core.eval(fargs[2], fargs[3]) # not a builtin, but worth treating specially elseif fargs[1] === Base.rethrow @@ -281,12 +272,7 @@ function evaluate_call_recurse!(@nospecialize(recurse), frame::Frame, call_expr: npc = newframe.pc shouldbreak(newframe, npc) && return BreakpointRef(newframe.framecode, npc) # if the following errors, handle_err will pop the stack and recycle newframe - if recurse === finish_and_return! - # Optimize this case to avoid dynamic dispatch - ret = finish_and_return!(finish_and_return!, newframe, false) - else - ret = recurse(recurse, newframe, false) - end + ret = finish_and_return!(interp, newframe, false) isa(ret, BreakpointRef) && return ret frame.callee = nothing return_from(newframe) @@ -294,22 +280,21 @@ function evaluate_call_recurse!(@nospecialize(recurse), frame::Frame, call_expr: end """ - ret = evaluate_call!(Compiled(), frame::Frame, call_expr) - ret = evaluate_call!(recurse, frame::Frame, call_expr) + ret = evaluate_call!(interp::Interpreter, frame::Frame, call_expr::Expr, enter_generated::Bool=false) + ret = evaluate_call!(frame::Frame, call_expr::Expr, enter_generated::Bool=false) Evaluate a `:call` expression `call_expr` in the context of `frame`. The first causes it to be executed using Julia's normal dispatch (compiled code), whereas the second recurses in via the interpreter. -`recurse` has a default value of [`JuliaInterpreter.finish_and_return!`](@ref). +`interp` has a default value of [`RecursiveInterpreter`](@ref). """ -evaluate_call!(::Compiled, frame::Frame, call_expr::Expr; kwargs...) = evaluate_call_compiled!(Compiled(), frame, call_expr; kwargs...) -evaluate_call!(@nospecialize(recurse), frame::Frame, call_expr::Expr; kwargs...) = evaluate_call_recurse!(recurse, frame, call_expr; kwargs...) -evaluate_call!(frame::Frame, call_expr::Expr; kwargs...) = evaluate_call!(finish_and_return!, frame, call_expr; kwargs...) +evaluate_call!(frame::Frame, call_expr::Expr, enter_generated::Bool=false) = + evaluate_call!(RecursiveInterpreter(), frame, call_expr, enter_generated) # The following come up only when evaluating toplevel code -function evaluate_methoddef(frame::Frame, node::Expr) +function evaluate_methoddef(interp::Interpreter, frame::Frame, node::Expr) mt = extract_method_table(frame, node) - mt !== nothing && return evaluate_overlayed_methoddef(frame, node, mt) + mt !== nothing && return evaluate_overlayed_methoddef(interp, frame, node, mt) f = node.args[1] if f isa Symbol || f isa GlobalRef mod = f isa Symbol ? moduleof(frame) : f.mod @@ -327,17 +312,17 @@ function evaluate_methoddef(frame::Frame, node::Expr) end end length(node.args) == 1 && return f - sig = lookup(frame, node.args[2])::SimpleVector - body = lookup(frame, node.args[3])::Union{CodeInfo, Expr} + sig = lookup(interp, frame, node.args[2])::SimpleVector + body = lookup(interp, frame, node.args[3])::Union{CodeInfo, Expr} method = ccall(:jl_method_def, Any, (Any, Ptr{Cvoid}, Any, Any), sig, C_NULL, body, moduleof(frame)::Module)::Method return method end -function evaluate_overlayed_methoddef(frame::Frame, node::Expr, mt::MethodTable) +function evaluate_overlayed_methoddef(interp::Interpreter, frame::Frame, node::Expr, mt::MethodTable) # Overlaying an empty function such as `function f end` is not legal, and `f` must # already be defined so we don't need to do as much work as in `evaluate_methoddef`. - sig = lookup(frame, node.args[2])::SimpleVector - body = lookup(frame, node.args[3])::Union{CodeInfo, Expr} + sig = lookup(interp, frame, node.args[2])::SimpleVector + body = lookup(interp, frame, node.args[3])::Union{CodeInfo, Expr} method = ccall(:jl_method_def, Any, (Any, Any, Any, Any), sig, mt, body, moduleof(frame)::Module)::Method return method end @@ -388,26 +373,24 @@ function maybe_assign!(frame::Frame, @nospecialize(stmt), @nospecialize(val)) end maybe_assign!(frame::Frame, @nospecialize(val)) = maybe_assign!(frame, pc_expr(frame), val) -function eval_rhs(@nospecialize(recurse), frame::Frame, node::Expr) +function eval_rhs(interp::Interpreter, frame::Frame, node::Expr) head = node.head if head === :new - args = Any[lookup(frame, arg) for arg in node.args] + args = Any[lookup(interp, frame, arg) for arg in node.args] T = popfirst!(args)::DataType rhs = ccall(:jl_new_structv, Any, (Any, Ptr{Any}, UInt32), T, args, length(args)) return rhs elseif head === :splatnew # Julia 1.2+ - T = lookup(frame, node.args[1])::DataType - args = lookup(frame, node.args[2])::Tuple + T = lookup(interp, frame, node.args[1])::DataType + args = lookup(interp, frame, node.args[2])::Tuple rhs = ccall(:jl_new_structt, Any, (Any, Any), T, args) return rhs elseif head === :isdefined return check_isdefined(frame, node.args[1]) elseif head === :call - # here it's crucial to avoid dynamic dispatch - isa(recurse, Compiled) && return evaluate_call_compiled!(recurse, frame, node) - return evaluate_call_recurse!(recurse, frame, node) + return evaluate_call!(interp, frame, node) elseif head === :foreigncall || head === :cfunction - return evaluate_foreigncall(recurse, frame, node) + return evaluate_foreigncall(interp, frame, node) elseif head === :copyast val = (node.args[1]::QuoteNode).value return isa(val, Expr) ? copy(val) : val @@ -418,9 +401,9 @@ function eval_rhs(@nospecialize(recurse), frame::Frame, node::Expr) head === :aliasscope || head === :popaliasscope return nothing elseif head === :method && length(node.args) == 1 - return evaluate_methoddef(frame, node) + return evaluate_methoddef(interp, frame, node) end - return lookup_expr(frame, node) + return lookup_expr(interp, frame, node) end function check_isdefined(frame::Frame, @nospecialize(node)) @@ -471,7 +454,7 @@ end # in `step_expr!` const _location = Dict{Tuple{Method,Int},Int}() -function step_expr!(@nospecialize(recurse), frame::Frame, @nospecialize(node), istoplevel::Bool) +function step_expr!(interp::Interpreter, frame::Frame, @nospecialize(node), istoplevel::Bool) pc, code, data = frame.pc, frame.framecode, frame.framedata # if !is_leaf(frame) # show_stackloc(frame) @@ -491,9 +474,9 @@ function step_expr!(@nospecialize(recurse), frame::Frame, @nospecialize(node), i if node.head === :(=) lhs, rhs = node.args if isa(rhs, Expr) - rhs = eval_rhs(recurse, frame, rhs) + rhs = eval_rhs(interp, frame, rhs) else - rhs = lookup(frame, rhs) + rhs = lookup(interp, frame, rhs) end isa(rhs, BreakpointRef) && return rhs do_assignment!(frame, lhs, rhs) @@ -523,7 +506,7 @@ function step_expr!(@nospecialize(recurse), frame::Frame, @nospecialize(node), i # (https://github.com/JuliaDebug/JuliaInterpreter.jl/issues/591) elseif istoplevel if node.head === :method && length(node.args) > 1 - rhs = evaluate_methoddef(frame, node) + rhs = evaluate_methoddef(interp, frame, node) elseif node.head === :module error("this should have been handled by split_expressions") elseif node.head === :using || node.head === :import || node.head === :export @@ -531,20 +514,13 @@ function step_expr!(@nospecialize(recurse), frame::Frame, @nospecialize(node), i elseif node.head === :const || node.head === :globaldecl g = node.args[1] if length(node.args) == 2 - Core.eval(moduleof(frame), Expr(:block, Expr(node.head, g, lookup(frame, node.args[2])), nothing)) + Core.eval(moduleof(frame), Expr(:block, Expr(node.head, g, lookup(interp, frame, node.args[2])), nothing)) else Core.eval(moduleof(frame), Expr(:block, Expr(node.head, g), nothing)) end elseif node.head === :thunk newframe = Frame(moduleof(frame), node.args[1]::CodeInfo) - if isa(recurse, Compiled) - finish!(recurse, newframe, true) - else - newframe.caller = frame - frame.callee = newframe - finish!(recurse, newframe, true) - frame.callee = nothing - end + finish_nested_frame!(interp, newframe, frame) return_from(newframe) elseif node.head === :global Core.eval(moduleof(frame), node) @@ -559,7 +535,7 @@ function step_expr!(@nospecialize(recurse), frame::Frame, @nospecialize(node), i end newframe = ($Frame)(mod, ex) while true - ($through_methoddef_or_done!)($recurse, newframe) === nothing && break + ($through_methoddef_or_done!)($interp, newframe) === nothing && break end $return_from(newframe) end))) @@ -570,18 +546,18 @@ function step_expr!(@nospecialize(recurse), frame::Frame, @nospecialize(node), i elseif node.head === :latestworld frame.world = Base.get_world_counter() else - rhs = eval_rhs(recurse, frame, node) + rhs = eval_rhs(interp, frame, node) end elseif node.head === :thunk || node.head === :toplevel error("this frame needs to be run at top level") else - rhs = eval_rhs(recurse, frame, node) + rhs = eval_rhs(interp, frame, node) end elseif isa(node, GotoNode) @assert is_leaf(frame) return (frame.pc = node.label) elseif isa(node, GotoIfNot) - arg = lookup(frame, node.cond) + arg = lookup(interp, frame, node.cond) if !isa(arg, Bool) throw(TypeError(nameof(frame), "if", Bool, arg)) end @@ -600,13 +576,13 @@ function step_expr!(@nospecialize(recurse), frame::Frame, @nospecialize(node), i rhs = node.catch_dest push!(data.exception_frames, rhs) if isdefined(node, :scope) - push!(data.current_scopes, lookup(frame, node.scope)) + push!(data.current_scopes, lookup(interp, frame, node.scope)) end else - rhs = lookup(frame, node) + rhs = lookup(interp, frame, node) end catch err - return handle_err(recurse, frame, err) + return handle_err(interp, frame, err) end @isdefined(rhs) && isa(rhs, BreakpointRef) && return rhs if isassign(frame, pc) @@ -621,25 +597,46 @@ function step_expr!(@nospecialize(recurse), frame::Frame, @nospecialize(node), i end """ - pc = step_expr!(recurse, frame, istoplevel=false) + pc = step_expr!(interp::Interpreter, frame, istoplevel=false) pc = step_expr!(frame, istoplevel=false) Execute the next statement in `frame`. `pc` is the new program counter, or `nothing` if execution terminates, or a [`BreakpointRef`](@ref) if execution hits a breakpoint. -`recurse` controls call evaluation; `recurse = Compiled()` evaluates :call expressions -by normal dispatch. The default value `recurse = finish_and_return!` will use recursive -interpretation. +`interp` controls call evaluation; `interp = NonRecursiveInterpreter()` evaluates :call +expressions by normal dispatch. +The default value `interp = RecursiveInterpreter()` will use recursive interpretation. If you are evaluating `frame` at module scope you should pass `istoplevel=true`. """ -step_expr!(@nospecialize(recurse), frame::Frame, istoplevel::Bool=false) = - step_expr!(recurse, frame, pc_expr(frame), istoplevel) -step_expr!(frame::Frame, istoplevel::Bool=false) = - step_expr!(finish_and_return!, frame, istoplevel) +step_expr!(interp::Interpreter, frame::Frame, istoplevel::Bool=false) = + step_expr!(interp, frame, pc_expr(frame), istoplevel) +step_expr!(frame::Frame, istoplevel::Bool=false) = step_expr!(RecursiveInterpreter(), frame, istoplevel) + +# TODO Is this interface really needed? + +""" + finish_nested_frame!(interp::Interpreter, newframe::Frame, frame::Frame) + +Finish evaluating `newframe` and return to `frame`. + +This function is used when `newframe` is a nested frame embedded within `frame`. +Specifically, it applies when `frame` contains a top‐level `Expr(:thunk)` and needs to +interpret the `CodeInfo` embedded in that thunk. Consequently, within this function +`newframe` is also treated as a top‐level frame during interpretation. +""" +function finish_nested_frame!(interp::Interpreter, newframe::Frame, frame::Frame) + newframe.caller = frame + frame.callee = newframe + finish!(interp, newframe, true) + frame.callee = nothing +end +function finish_nested_frame!(interp::NonRecursiveInterpreter, newframe::Frame, ::Frame) + finish!(interp, newframe, true) +end """ - loc = handle_err(recurse, frame, err) + loc = handle_err(interp, frame, err) Deal with an error `err` that arose while evaluating `frame`. There are one of three behaviors: @@ -650,7 +647,7 @@ behaviors: `loc` is a `BreakpointRef`; - otherwise, `err` gets rethrown. """ -function handle_err(@nospecialize(recurse), frame::Frame, @nospecialize(err)) +function handle_err(::Interpreter, frame::Frame, @nospecialize(err)) data = frame.framedata err_will_be_thrown_to_top_level = isempty(data.exception_frames) && !data.caller_will_catch_err if break_on_throw[] || (break_on_error[] && err_will_be_thrown_to_top_level) @@ -679,18 +676,19 @@ function handle_err(@nospecialize(recurse), frame::Frame, @nospecialize(err)) return pc end -lookup_return(frame::Frame, node::ReturnNode) = lookup(frame, node.val) +lookup_return(interp::Interpreter, frame::Frame, node::ReturnNode) = lookup(interp, frame, node.val) """ - ret = get_return(frame) + ret = get_return(interp, frame) Get the return value of `frame`. Throws an error if `frame.pc` does not point to a `return` expression. `frame` must have already been executed so that the return value has been computed (see, e.g., [`JuliaInterpreter.finish!`](@ref)). """ -function get_return(frame) +function get_return(interp::Interpreter, frame::Frame) node = pc_expr(frame) is_return(node) || @invokelatest error("expected return statement, got ", node) - return lookup_return(frame, node) + return lookup_return(interp, frame, node) end +get_return(frame::Frame) = get_return(RecursiveInterpreter(), frame) get_return(t::Tuple{Module,Expr,Frame}) = get_return(t[end]) diff --git a/src/optimize.jl b/src/optimize.jl index 0f71684a..98a57304 100644 --- a/src/optimize.jl +++ b/src/optimize.jl @@ -181,7 +181,7 @@ function build_compiled_llvmcall!(stmt::Expr, code::CodeInfo, idx::Int, evalmod: frame.pc = idxstart if idxstart < idx while true - pc = step_expr!(Compiled(), frame) + pc = step_expr!(NonRecursiveInterpreter(), frame) pc === idx && break pc === nothing && error("this should never happen") end diff --git a/src/packagedef.jl b/src/packagedef.jl index 9a77b9d3..f26d8844 100644 --- a/src/packagedef.jl +++ b/src/packagedef.jl @@ -10,9 +10,11 @@ using Random using Random.DSFMT using InteractiveUtils -export @interpret, Compiled, Frame, root, leaf, ExprSplitter, - BreakpointRef, breakpoint, @breakpoint, breakpoints, enable, disable, remove, toggle, - debug_command, @bp, break_on, break_off, on_breakpoints_updated +export BreakpointRef, Compiled, ExprSplitter, Frame, + Interpreter, NonRecursiveInterpreter, RecursiveInterpreter +export @bp, @breakpoint, @interpret, + break_off, break_on, breakpoint, breakpoints, debug_command, disable, enable, leaf, + on_breakpoints_updated, remove, root, toggle module CompiledCalls # This module is for handling intrinsics that must be compiled (llvmcall) as well as ccalls diff --git a/src/types.jl b/src/types.jl index eccd4984..d86a40e6 100644 --- a/src/types.jl +++ b/src/types.jl @@ -1,9 +1,42 @@ """ -`Compiled` is a trait indicating that any `:call` expressions should be evaluated -using Julia's normal compiled-code evaluation. The alternative is to pass `stack=Frame[]`, -which will cause all calls to be evaluated via the interpreter. + abstract type Interpreter end + +An interpreter that subtypes this type can implement its own evaluation strategies, by +overloading the certain methods in JuliaInterpreter that are defined for this base type. +The default behavior of `Interpreter` is same as that of [`RecursiveInterpreter`](@ref), +meaning it will recursively interpret all `:call` expressions. +""" +abstract type Interpreter end + +""" + RecursiveInterpreter <: Interpreter + +`RecursiveInterpreter` is an [`Interpreter`](@ref) that recursively interprets any `:call` +expressions in the code being interpreted. + +With this interpreter, code runs in fully interpreted mode; it will never be compiled for execution. +""" +struct RecursiveInterpreter <: Interpreter end + +""" + NonRecursiveInterpreter <: Interpreter + +`NonRecursiveInterpreter` is an [`Interpreter`](@ref) that evaluates any `:call` expressions +in the code being interpreted using Julia's normal code execution engine with the native +compiler. + +`JuliaInterpreter.Compiled` is aliased to `NonRecursiveInterpreter` for backward compatibility. +""" +struct NonRecursiveInterpreter <: Interpreter end + +""" + const Compiled = NonRecursiveInterpreter + +As of JuliaInterpreter v0.10, `Compiled` is now an alias for [`NonRecursiveInterpreter`](@ref). +This remains for backward compatibility for packages using `Compiled`, and may be removed or +redefined as a completely different type in v0.11 or later. """ -struct Compiled end +const Compiled = NonRecursiveInterpreter # for backward compatibility Base.similar(::Compiled, sz) = Compiled() # to support similar(stack, 0) # Our own replacements for Core types. We need to do this to ensure we can tell the difference diff --git a/test/debug.jl b/test/debug.jl index f6bd2732..94f1634a 100644 --- a/test/debug.jl +++ b/test/debug.jl @@ -7,7 +7,7 @@ const ALL_COMMANDS = (:n, :s, :c, :finish, :nc, :se, :si, :until) function step_through_command(fr::Frame, cmd::Symbol) while true - ret = JuliaInterpreter.debug_command(JuliaInterpreter.finish_and_return!, fr, cmd) + ret = JuliaInterpreter.debug_command(fr, cmd) ret == nothing && break fr, pc = ret end @@ -336,7 +336,7 @@ end try break_on(:error) fr = JuliaInterpreter.enter_call(f_outer) - fr, pc = debug_command(JuliaInterpreter.finish_and_return!, fr, :finish) + fr, pc = debug_command(fr, :finish) @test fr.framecode.scope.name === :error fundef() = undef_func() diff --git a/test/interpret.jl b/test/interpret.jl index 97144dbc..1b13ec55 100644 --- a/test/interpret.jl +++ b/test/interpret.jl @@ -361,25 +361,24 @@ f113(;x) = x frame = JuliaInterpreter.enter_call(f_multi, 1) nlocals = length(frame.framedata.locals) @test_throws UndefVarError JuliaInterpreter.lookup_var(frame, JuliaInterpreter.SlotNumber(nlocals)) - stack = [frame] locals = JuliaInterpreter.locals(frame) @test length(locals) == 2 @test JuliaInterpreter.Variable(1, :x, false) in locals - JuliaInterpreter.step_expr!(stack, frame) - JuliaInterpreter.step_expr!(stack, frame) + JuliaInterpreter.step_expr!(frame) + JuliaInterpreter.step_expr!(frame) @static if VERSION >= v"1.11-" locals = JuliaInterpreter.locals(frame) @test length(locals) == 2 - JuliaInterpreter.step_expr!(stack, frame) + JuliaInterpreter.step_expr!(frame) end locals = JuliaInterpreter.locals(frame) @test length(locals) == 3 @test JuliaInterpreter.Variable(1, :c, false) in locals - JuliaInterpreter.step_expr!(stack, frame) + JuliaInterpreter.step_expr!(frame) locals = JuliaInterpreter.locals(frame) @test length(locals) == 3 @test JuliaInterpreter.Variable(2, :x, false) in locals - JuliaInterpreter.step_expr!(stack, frame) + JuliaInterpreter.step_expr!(frame) locals = JuliaInterpreter.locals(frame) @test length(locals) == 3 @test JuliaInterpreter.Variable(3, :x, false) in locals @@ -962,3 +961,17 @@ func_arrayref(a, i) = Core.arrayref(true, a, i) @static if isdefinedglobal(Base, :ScopedValues) @testset "interpret_scopedvalues.jl" include("interpret_scopedvalues.jl") end + +@testset "changing interpreter for @interpret" begin + @test sin(42) == @interpret sin(42) + @test sin(42) == @interpret interp=RecursiveInterpreter() sin(42) + @test sin(42) == @interpret interp=NonRecursiveInterpreter() sin(42) + @test ((@allocated @interpret interp=RecursiveInterpreter() sin(42)) ≠ (@allocated @interpret interp=NonRecursiveInterpreter() sin(42))) + let interp1 = RecursiveInterpreter(), + interp2 = NonRecursiveInterpreter() + @test sin(42) == @interpret interp=interp1 sin(42) + @test sin(42) == @interpret interp=interp2 sin(42) + end + @test_throws "Invalid @interpret call" macroexpand(@__MODULE__, :(@interpret interp sin(42))) + @test_throws "Invalid @interpret call" macroexpand(@__MODULE__, :(@interpret _interp_=RecursiveInterpreter() sin(42))) +end diff --git a/test/interpret_scopedvalues.jl b/test/interpret_scopedvalues.jl index 25de35bb..e010b4a3 100644 --- a/test/interpret_scopedvalues.jl +++ b/test/interpret_scopedvalues.jl @@ -33,7 +33,7 @@ let m = only(methods(_sval1_func2)) end end let frame = JuliaInterpreter.enter_call(sval1_func2) - @test 2 == JuliaInterpreter.finish_and_return!(Compiled(), frame) + @test 2 == JuliaInterpreter.finish_and_return!(NonRecursiveInterpreter(), frame) end # preset `current_scope` support diff --git a/test/limits.jl b/test/limits.jl index 74e060eb..68f8ba66 100644 --- a/test/limits.jl +++ b/test/limits.jl @@ -99,7 +99,7 @@ module EvalLimited end @test isa(frame, Frame) nstmtsleft = nstmts while true - ret, nstmtsleft = evaluate_limited!(Compiled(), frame, nstmtsleft, true) + ret, nstmtsleft = evaluate_limited!(NonRecursiveInterpreter(), frame, nstmtsleft, true) isa(ret, Some{Any}) && break isa(ret, Aborted) && (push!(aborts, ret); break) end diff --git a/test/utils.jl b/test/utils.jl index 6d2cee35..ed515e43 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -1,5 +1,5 @@ using JuliaInterpreter -using JuliaInterpreter: Frame +using JuliaInterpreter: Frame, Interpreter, RecursiveInterpreter using JuliaInterpreter: finish_and_return!, evaluate_call!, step_expr!, shouldbreak, do_assignment!, SSAValue, isassign, pc_expr, handle_err, get_return, moduleof @@ -23,7 +23,7 @@ function stacklength(frame) end # Execute a frame using Julia's regular compiled-code dispatch for any :call expressions -runframe(frame) = Some{Any}(finish_and_return!(Compiled(), frame)) +runframe(frame) = Some{Any}(finish_and_return!(NonRecursiveInterpreter(), frame)) # Execute a frame using the interpreter for all :call expressions (except builtins & intrinsics) runstack(frame) = Some{Any}(finish_and_return!(frame)) @@ -50,82 +50,85 @@ function Aborted(frame::Frame, pc) return Aborted(lineinfo) end +mutable struct LimitedExec <: Interpreter + nstmts::Int +end + """ - ret, nstmtsleft = evaluate_limited!(recurse, frame, nstmts, istoplevel::Bool=true) + ret, nstmtsleft = evaluate_limited!(interp::Interpreter, frame, nstmts, istoplevel::Bool=true) Run `frame` until one of: - execution terminates normally (`ret = Some{Any}(val)`, where `val` is the returned value of `frame`) - if `istoplevel` and a `thunk` or `method` expression is encountered (`ret = nothing`) - more than `nstmts` have been executed (`ret = Aborted(lin)`, where `lnn` is the `LineInfoNode` of termination). """ -function evaluate_limited!(@nospecialize(recurse), frame::Frame, nstmts::Int, istoplevel::Bool=false) - refnstmts = Ref(nstmts) - limexec!(s, f, istl) = limited_exec!(s, f, refnstmts, istl) +function evaluate_limited!(interp::Interpreter, frame::Frame, nstmts::Int, istoplevel::Bool=false) + limited_interp = LimitedExec(nstmts) # The following is like finish!, except we intercept :call expressions so that we can run them # with limexec! rather than the default finish_and_return! pc = frame.pc while nstmts > 0 - shouldbreak(frame, pc) && return BreakpointRef(frame.framecode, pc), refnstmts[] + shouldbreak(frame, pc) && return BreakpointRef(frame.framecode, pc), limited_interp.nstmts stmt = pc_expr(frame, pc) # uncomment the following to calibrate `nstmts` in test/limits.jl # _lnn_ = Aborted(frame, pc).at # _lnn_.file == Symbol("fake.jl") && _lnn_.line == 5 && isa(stmt, Core.GotoIfNot) && @show nstmts if isa(stmt, Expr) - if stmt.head === :call && !isa(recurse, Compiled) - refnstmts[] = nstmts + if stmt.head === :call && !isa(interp, NonRecursiveInterpreter) + limited_interp.nstmts = nstmts try - rhs = evaluate_call!(limexec!, frame, stmt) - isa(rhs, Aborted) && return rhs, refnstmts[] + rhs = evaluate_call!(limited_interp, frame, stmt) + isa(rhs, Aborted) && return rhs, limited_interp.nstmts lhs = SSAValue(pc) do_assignment!(frame, lhs, rhs) new_pc = pc + 1 catch err - new_pc = handle_err(recurse, frame, err) + new_pc = handle_err(interp, frame, err) end - nstmts = refnstmts[] - elseif stmt.head === :(=) && isexpr(stmt.args[2], :call) && !isa(recurse, Compiled) - refnstmts[] = nstmts + nstmts = limited_interp.nstmts + elseif stmt.head === :(=) && isexpr(stmt.args[2], :call) && !isa(interp, NonRecursiveInterpreter) + limited_interp.nstmts = nstmts try - rhs = evaluate_call!(limexec!, frame, stmt.args[2]) - isa(rhs, Aborted) && return rhs, refnstmts[] + rhs = evaluate_call!(limited_interp, frame, stmt.args[2]) + isa(rhs, Aborted) && return rhs, limited_interp.nstmts do_assignment!(frame, stmt.args[1], rhs) new_pc = pc + 1 catch err - new_pc = handle_err(recurse, frame, err) + new_pc = handle_err(interp, frame, err) end - nstmts = refnstmts[] + nstmts = limited_interp.nstmts elseif istoplevel && stmt.head === :thunk code = stmt.args[1] if length(code.code) == 1 && JuliaInterpreter.is_return(code.code[end]) && isexpr(code.code[end].args[1], :method) # Julia 1.2+ puts a :thunk before the start of each method new_pc = pc + 1 else - refnstmts[] = nstmts + limited_interp.nstmts = nstmts newframe = Frame(moduleof(frame), stmt) - if isa(recurse, Compiled) - finish!(recurse, newframe, true) + if isa(interp, NonRecursiveInterpreter) + finish!(interp, newframe, true) else newframe.caller = frame frame.callee = newframe - ret = limited_exec!(recurse, newframe, refnstmts, istoplevel) - isa(ret, Aborted) && return ret, refnstmts[] + ret = finish_and_return!(limited_interp, newframe, true) + isa(ret, Aborted) && return ret, limited_interp.nstmts frame.callee = nothing end JuliaInterpreter.recycle(newframe) # Because thunks may define new methods, return to toplevel frame.pc = pc + 1 - return nothing, refnstmts[] + return nothing, limited_interp.nstmts end elseif istoplevel && stmt.head === :method && length(stmt.args) == 3 - step_expr!(recurse, frame, stmt, istoplevel) + step_expr!(interp, frame, stmt, istoplevel) frame.pc = pc + 1 return nothing, nstmts - 1 else - new_pc = step_expr!(recurse, frame, stmt, istoplevel) + new_pc = step_expr!(interp, frame, stmt, istoplevel) nstmts -= 1 end else - new_pc = step_expr!(recurse, frame, stmt, istoplevel) + new_pc = step_expr!(interp, frame, stmt, istoplevel) nstmts -= 1 end (new_pc === nothing || isa(new_pc, BreakpointRef)) && break @@ -141,24 +144,24 @@ function evaluate_limited!(@nospecialize(recurse), frame::Frame, nstmts::Int, is return Some{Any}(ret), nstmts end -evaluate_limited!(@nospecialize(recurse), modex::Tuple{Module,Expr,Frame}, nstmts::Int, istoplevel::Bool=true) = - evaluate_limited!(recurse, modex[end], nstmts, istoplevel) -evaluate_limited!(@nospecialize(recurse), modex::Tuple{Module,Expr,Expr}, nstmts::Int, istoplevel::Bool=true) = +evaluate_limited!(interp::Interpreter, modex::Tuple{Module,Expr,Frame}, nstmts::Int, istoplevel::Bool=true) = + evaluate_limited!(interp, modex[end], nstmts, istoplevel) +evaluate_limited!(interp::Interpreter, modex::Tuple{Module,Expr,Expr}, nstmts::Int, istoplevel::Bool=true) = Some{Any}(Core.eval(modex[1], modex[3])), nstmts evaluate_limited!(frame::Union{Frame, Tuple}, nstmts::Int, istoplevel::Bool=false) = - evaluate_limited!(finish_and_return!, frame, nstmts, istoplevel) + evaluate_limited!(RecursiveInterpreter(), frame, nstmts, istoplevel) -function limited_exec!(@nospecialize(recurse), newframe, refnstmts, istoplevel) - ret, nleft = evaluate_limited!(recurse, newframe, refnstmts[], istoplevel) - refnstmts[] = nleft +function JuliaInterpreter.finish_and_return!(interp::LimitedExec, newframe::Frame, istoplevel::Bool) + ret, nleft = evaluate_limited!(interp, newframe, interp.nstmts, istoplevel) + interp.nstmts = nleft return isa(ret, Aborted) ? ret : something(ret) end ### Functions needed on workers for running tests function configure_test() - # To run tests efficiently, certain methods must be run in Compiled mode, + # To run tests efficiently, certain methods must be run in the compiled mode, # in particular those that are used by the Test infrastructure cm = JuliaInterpreter.compiled_methods empty!(cm) @@ -202,7 +205,7 @@ function run_test_by_eval(test, fullpath, nstmts) ret, nstmtsleft = evaluate_limited!(frame, nstmtsleft, true) if isa(ret, Aborted) push!(aborts, ret) - JuliaInterpreter.finish_stack!(Compiled(), frame, true) + JuliaInterpreter.finish_stack!(NonRecursiveInterpreter(), frame, true) end end println("Finished ", $test)