-
Notifications
You must be signed in to change notification settings - Fork 35
/
utils.jl
216 lines (201 loc) · 9.07 KB
/
utils.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
using JuliaInterpreter
using JuliaInterpreter: JuliaStackFrame, JuliaProgramCounter, @lookup
using JuliaInterpreter: finish_and_return!, @lookup, evaluate_call!, _step_expr!,
do_assignment!, getlhs, isassign, pc_expr, handle_err, get_return,
moduleof, prepare_thunk
using Base.Meta: isexpr
using Test, Random
# Execute a frame using Julia's regular compiled-code dispatch for any :call expressions
runframe(frame, pc=frame.pc[]) = Some{Any}(finish_and_return!(Compiled(), frame, pc))
# Execute a frame using the interpreter for all :call expressions (except builtins & intrinsics)
function runstack(frame::JuliaStackFrame, pc=frame.pc[])
stack = JuliaStackFrame[]
return Some{Any}(finish_and_return!(stack, frame, pc))
end
## For juliatests.jl
function read_and_parse(filename)
src = read(filename, String)
ex = Base.parse_input_line(src; filename=filename)
end
## For running interpreter frames under resource limitations
struct Aborted # for signaling that some statement or test blocks were interrupted
at::Core.LineInfoNode
end
function Aborted(frame::JuliaStackFrame, pc)
lineidx = frame.code.code.codelocs[convert(Int, pc)]
return Aborted(frame.code.code.linetable[lineidx])
end
"""
ret, nstmtsleft = evaluate_limited!(stack, 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!(stack, frame::JuliaStackFrame, nstmts::Int, pc::JuliaProgramCounter, istoplevel::Bool)
refnstmts = Ref(nstmts)
limexec!(s,f) = limited_exec!(s, f, refnstmts, istoplevel)
# 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!
while nstmts > 0
stmt = pc_expr(frame, pc)
if isa(stmt, Expr)
if stmt.head == :call && !isa(stack, Compiled)
refnstmts[] = nstmts
try
rhs = evaluate_call!(stack, frame, stmt, pc; exec! = limexec!)
isa(rhs, Aborted) && return rhs, refnstmts[]
lhs = getlhs(pc)
do_assignment!(frame, lhs, rhs)
new_pc = pc + 1
catch err
new_pc = handle_err(frame, err)
end
nstmts = refnstmts[]
elseif stmt.head == :(=) && isexpr(stmt.args[2], :call) && !isa(stack, Compiled)
refnstmts[] = nstmts
try
rhs = evaluate_call!(stack, frame, stmt.args[2], pc; exec! = limexec!)
isa(rhs, Aborted) && return rhs, refnstmts[]
do_assignment!(frame, stmt.args[1], rhs)
new_pc = pc + 1
catch err
new_pc = handle_err(frame, err)
end
nstmts = refnstmts[]
elseif stmt.head == :thunk
code = stmt.args[1]
if length(code.code) == 1 && isexpr(code.code[end], :return) && isexpr(code.code[end].args[1], :method)
# Julia 1.2+ puts a :thunk before the start of each method
new_pc = pc + 1
else
newframe = prepare_thunk(moduleof(frame), stmt)
frame.pc[] = pc
push!(stack, frame)
refnstmts[] = nstmts
ret = limited_exec!(stack, newframe, refnstmts, istoplevel)
isa(ret, Aborted) && return ret, refnstmts[]
pop!(stack)
push!(JuliaInterpreter.junk, newframe) # rather than going through GC, just re-use it
frame.pc[] = pc + 1
return nothing, refnstmts[]
end
elseif stmt.head == :method && length(stmt.args) == 3
_step_expr!(stack, frame, stmt, pc, istoplevel)
frame.pc[] = pc + 1
return nothing, nstmts - 1
else
# try
new_pc = _step_expr!(stack, frame, stmt, pc, istoplevel)
# catch err
# dump(stmt)
# @show istoplevel pc stmt frame
# rethrow(err)
# end
nstmts -= 1
end
else
new_pc = _step_expr!(stack, frame, stmt, pc, istoplevel)
nstmts -= 1
end
new_pc == nothing && break
pc = new_pc
end
frame.pc[] = pc
# Handle the return
stmt = pc_expr(frame, pc)
if nstmts == 0 && !isexpr(stmt, :return)
ret = Aborted(frame, pc)
return ret, nstmts
end
ret = get_return(frame, pc)
return Some{Any}(ret), nstmts
end
evaluate_limited!(stack, frame::JuliaStackFrame, nstmts::Int, istoplevel::Bool=true) =
evaluate_limited!(stack, frame, nstmts, frame.pc[], istoplevel)
evaluate_limited!(stack, modex::Tuple{Module,Expr,JuliaStackFrame}, nstmts::Int, istoplevel::Bool=true) =
evaluate_limited!(stack, modex[end], nstmts, istoplevel)
evaluate_limited!(stack, modex::Tuple{Module,Expr,Expr}, nstmts::Int, istoplevel::Bool=true) =
Some{Any}(Core.eval(modex[1], modex[3])), nstmts
function limited_exec!(stack, newframe, refnstmts, istoplevel)
ret, nleft = evaluate_limited!(stack, newframe, refnstmts[], newframe.pc[], istoplevel)
refnstmts[] = 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,
# in particular those that are used by the Test infrastructure
cm = JuliaInterpreter.compiled_methods
empty!(cm)
push!(cm, which(Test.eval_test, Tuple{Expr, Expr, LineNumberNode}))
push!(cm, which(Test.get_testset, Tuple{}))
push!(cm, which(Test.push_testset, Tuple{Test.AbstractTestSet}))
push!(cm, which(Test.pop_testset, Tuple{}))
for f in (Test.record, Test.finish)
for m in methods(f)
push!(cm, m)
end
end
push!(cm, which(Random.seed!, Tuple{Union{Integer,Vector{UInt32}}}))
push!(cm, which(copy!, Tuple{Random.MersenneTwister, Random.MersenneTwister}))
push!(cm, which(copy, Tuple{Random.MersenneTwister}))
push!(cm, which(Base.include, Tuple{Module, String}))
push!(cm, which(Base.show_backtrace, Tuple{IO, Vector}))
push!(cm, which(Base.show_backtrace, Tuple{IO, Vector{Any}}))
end
function run_test_by_eval(test, fullpath, nstmts)
Core.eval(Main, Expr(:toplevel, :(module JuliaTests using Test, Random end), quote
# These must be run at top level, so we can't put this in a function
println("Working on ", $test, "...")
ex = read_and_parse($fullpath)
isexpr(ex, :error) && @error "error parsing $($test): $ex"
aborts = Aborted[]
ts = Test.DefaultTestSet($test)
Test.push_testset(ts)
current_task().storage[:SOURCE_PATH] = $fullpath
modexs, _ = JuliaInterpreter.prepare_toplevel(JuliaTests, ex)
stack = JuliaStackFrame[]
for (i, modex) in enumerate(modexs) # having the index can be useful for debugging
nstmtsleft = $nstmts
# mod, ex = modex
# @show mod ex
frame = JuliaInterpreter.prepare_thunk(modex)
while true
yield() # allow communication between processes
ret, nstmtsleft = evaluate_limited!(stack, frame, nstmtsleft)
isa(ret, Some{Any}) && break
if isa(ret, Aborted)
push!(aborts, ret)
# run the remaining statements in Compiled mode, thus allowing later tests
# to work. Largely fixes #30. Of course this is not perfect, because it
# may repeat some work done previously, but it's a decent start.
# TODO: recurse over stack. The key problem is that the inner-most frame is lost.
pc = frame.pc[]
while true # This is finish!, except we need to run it at top level
new_pc = _step_expr!(Compiled(), frame, pc, true)
new_pc == nothing && break
pc = new_pc
end
frame.pc[] = pc
break
end
end
end
println("Finished ", $test)
return ts, aborts
end))
end
# To help debugging
function run_compiled(frame)
Core.eval(moduleof(frame), Expr(:toplevel, quote
let pc = $frame.pc[]
while true # This is finish!, except we need to run it at top level
new_pc = ($_step_expr!)($(Compiled()), $frame, pc, true)
new_pc == nothing && break
pc = new_pc
end
$frame.pc[] = pc
end
end))
end