Skip to content

Commit 7705307

Browse files
authored
Merge pull request #41 from JuliaDebug/teh/world_age
Fix world age problems when running tests
2 parents 31ea1d4 + 2c64810 commit 7705307

16 files changed

+725
-362
lines changed

Manifest.toml

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
[[Base64]]
44
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
55

6-
[[Distributed]]
7-
deps = ["Random", "Serialization", "Sockets"]
8-
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
6+
[[Dates]]
7+
deps = ["Printf"]
8+
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
99

1010
[[InteractiveUtils]]
1111
deps = ["Markdown"]
@@ -15,12 +15,9 @@ uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
1515
deps = ["Base64"]
1616
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
1717

18-
[[Random]]
19-
deps = ["Serialization"]
20-
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
18+
[[Printf]]
19+
deps = ["Unicode"]
20+
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
2121

22-
[[Serialization]]
23-
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
24-
25-
[[Sockets]]
26-
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
22+
[[Unicode]]
23+
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ version = "0.1.1"
66
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
77

88
[extras]
9+
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
910
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
1011
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1112
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1213

1314
[targets]
14-
test = ["Test", "Distributed", "Random"]
15+
test = ["Test", "Distributed", "Random", "Dates"]

docs/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
33
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
44
JuliaInterpreter = "aa1ae85d-cabe-5617-a682-6adf51b2e16a"
5+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

docs/make.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Documenter, JuliaInterpreter
1+
using Documenter, JuliaInterpreter, Test
22

33
makedocs(
44
modules = [JuliaInterpreter],

docs/src/dev_reference.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ JuliaInterpreter.build_frame
1515
JuliaInterpreter.determine_method_for_expr
1616
JuliaInterpreter.prepare_args
1717
JuliaInterpreter.prepare_call
18+
JuliaInterpreter.prepare_thunk
19+
JuliaInterpreter.prepare_toplevel
1820
JuliaInterpreter.get_call_framecode
1921
JuliaInterpreter.optimize!
2022
```
@@ -26,7 +28,9 @@ JuliaInterpreter.Compiled
2628
JuliaInterpreter.step_expr!
2729
JuliaInterpreter.finish!
2830
JuliaInterpreter.finish_and_return!
31+
JuliaInterpreter.get_return
2932
JuliaInterpreter.next_until!
33+
JuliaInterpreter.through_methoddef_or_done!
3034
JuliaInterpreter.evaluate_call!
3135
JuliaInterpreter.evaluate_foreigncall!
3236
JuliaInterpreter.maybe_evaluate_builtin
@@ -45,10 +49,12 @@ JuliaInterpreter.JuliaProgramCounter
4549
```@docs
4650
JuliaInterpreter.framedict
4751
JuliaInterpreter.genframedict
52+
JuliaInterpreter.compiled_methods
4853
```
4954

5055
## Utilities
5156

5257
```@docs
58+
JuliaInterpreter.@lookup
5359
JuliaInterpreter.iswrappercall
5460
```

docs/src/internals.md

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Internals
22

3+
## Basic usage
4+
35
The process of executing code in the interpreter is to prepare a `frame` and then
46
evaluate these statements one-by-one, branching via the `goto` statements as appropriate.
57
Using the `summer` example described in [Lowered representation](@ref),
@@ -150,4 +152,117 @@ julia> frame.ssavalues
150152
```
151153
152154
One can easily continue this until execution completes, which is indicated when `step_expr!`
153-
returns `nothing`.
155+
returns `nothing`. Alternatively, use the higher-level `JuliaInterpreter.finish!(stack, frame)`
156+
to step through the entire frame,
157+
or `JuliaInterpreter.finish_and_return!(stack, frame)` to also obtain the return value.
158+
159+
## More complex expressions
160+
161+
Sometimes you might have a whole sequence of expressions you want to run.
162+
In such cases, your first thought should be `prepare_thunk`.
163+
Here's a demonstration:
164+
165+
```jldoctest; setup=(using JuliaInterpreter; empty!(JuliaInterpreter.junk))
166+
using Test
167+
168+
ex = quote
169+
x, y = 1, 2
170+
@test x + y == 3
171+
end
172+
173+
frame = JuliaInterpreter.prepare_thunk(Main, ex)
174+
JuliaInterpreter.finish_and_return!(JuliaStackFrame[], frame)
175+
176+
# output
177+
178+
Test Passed
179+
```
180+
181+
## Toplevel code and world age
182+
183+
Code that defines new `struct`s, new methods, or new modules is a bit more complicated
184+
and requires special handling. In such cases, calling `finish_and_return!` on a frame that
185+
defines these new objects and then calls them can trigger a
186+
[world age error](https://docs.julialang.org/en/latest/manual/methods/#Redefining-Methods-1),
187+
in which the method is considered to be too new to be run by the currently compiled code.
188+
While one can resolve this by using `Base.invokelatest`, we'd have to use that strategy
189+
throughout the entire package. This would cause a major reduction in performance.
190+
To resolve this issue without leading to performance problems, care is required to
191+
return to "top level" after defining such objects. This leads to altered syntax for executing
192+
such expressions.
193+
194+
Here's a demonstration of the problem:
195+
196+
```julia
197+
ex = :(map(x->x^2, [1, 2, 3]))
198+
frame = JuliaInterpreter.prepare_thunk(Main, ex)
199+
julia> JuliaInterpreter.finish_and_return!(JuliaStackFrame[], frame)
200+
ERROR: this frame needs to be run a top level
201+
```
202+
203+
The reason for this error becomes clearer if we examine `frame` or look directly at the lowered code:
204+
205+
```julia
206+
julia> Meta.lower(Main, ex)
207+
:($(Expr(:thunk, CodeInfo(
208+
1$(Expr(:thunk, CodeInfo(
209+
1global ##17#18
210+
const ##17#18
211+
$(Expr(:struct_type, Symbol("##17#18"), :((Core.svec)()), :((Core.svec)()), :(Core.Function), :((Core.svec)()), false, 0))
212+
└── return
213+
)))
214+
%2 = (Core.svec)(##17#18, Core.Any)
215+
%3 = (Core.svec)()
216+
%4 = (Core.svec)(%2, %3)
217+
$(Expr(:method, false, :(%4), CodeInfo(quote
218+
(Core.apply_type)(Base.Val, 2)
219+
(%1)()
220+
(Base.literal_pow)(^, x, %2)
221+
return %3
222+
end)))
223+
#17 = %new(##17#18)
224+
%7 = #17
225+
%8 = (Base.vect)(1, 2, 3)
226+
%9 = map(%7, %8)
227+
└── return %9
228+
))))
229+
```
230+
231+
All of the code before the `%7` line is devoted to defining the anonymous function `x->x^2`:
232+
it creates a new "anonymous type" (here written as `##17#18`), and then defines a "call
233+
function" for this type, equivalent to `(##17#18)(x) = x^2`.
234+
235+
In some cases one can fix this simply by indicating that we want to run this frame at top level:
236+
237+
```julia
238+
julia> JuliaInterpreter.finish_and_return!(JuliaStackFrame[], frame, true)
239+
3-element Array{Int64,1}:
240+
1
241+
4
242+
9
243+
```
244+
245+
Here's a more fine-grained look at what's happening under the hood (and a robust strategy
246+
for more complex situations where there may be nested calls of new methods):
247+
248+
```julia
249+
modexs, _ = JuliaInterpreter.prepare_toplevel(Main, ex)
250+
stack = JuliaStackFrame[]
251+
for (mod, e) in modexs
252+
frame = JuliaInterpreter.prepare_thunk(mod, e)
253+
while true
254+
JuliaInterpreter.through_methoddef_or_done!(stack, frame) === nothing && break
255+
end
256+
JuliaInterpreter.get_return(frame)
257+
end
258+
```
259+
260+
This splits the expression into a sequence of frames (here just one, but more complex blocks may be split up into many).
261+
Then, each frame is executed until it finishes defining a new method, then returns to top level.
262+
The return to top level causes an update in the world age.
263+
If the frame hasn't been finished yet (if the return value wasn't `nothing`),
264+
this continues executing where it left off.
265+
266+
(Incidentally, `JuliaInterpreter.enter_call(map, x->x^2, [1, 2, 3])` works fine on its own,
267+
because the anonymous function is defined by the caller---you'll see that the created frame
268+
is very simple.)

0 commit comments

Comments
 (0)