Skip to content

Commit cdc8b79

Browse files
Merge pull request #22 from c42f/cjf/constructor-scope-take-2
Separate RGF code cache from context for variable lookup
2 parents cee1c8a + 2c3fbed commit cdc8b79

File tree

3 files changed

+74
-51
lines changed

3 files changed

+74
-51
lines changed

src/RuntimeGeneratedFunctions.jl

Lines changed: 58 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,32 @@ module RuntimeGeneratedFunctions
22

33
using ExprTools, Serialization, SHA
44

5-
export RuntimeGeneratedFunction, @RuntimeGeneratedFunction
5+
export @RuntimeGeneratedFunction
66

77

88
"""
9-
RuntimeGeneratedFunction(module, function_expression)
9+
RuntimeGeneratedFunction
1010
11-
Construct a function from `function_expression` in the scope of `module` which
12-
can be called immediately without world age problems. Somewhat like using
13-
`eval(function_expression)` and then calling the resulting function. The
14-
differences are:
11+
This type should be constructed via the macro @RuntimeGeneratedFunction.
12+
"""
13+
struct RuntimeGeneratedFunction{argnames, cache_tag, context_tag, id} <: Function
14+
body::Expr
15+
function RuntimeGeneratedFunction(cache_tag, context_tag, ex)
16+
def = splitdef(ex)
17+
args, body = normalize_args(def[:args]), def[:body]
18+
id = expr_to_id(body)
19+
cached_body = _cache_body(cache_tag, id, body)
20+
new{Tuple(args), cache_tag, context_tag, id}(cached_body)
21+
end
22+
end
23+
24+
"""
25+
@RuntimeGeneratedFunction(function_expression)
26+
@RuntimeGeneratedFunction(context_module, function_expression)
27+
28+
Construct a function from `function_expression` which can be called immediately
29+
without world age problems. Somewhat like using `eval(function_expression)` and
30+
then calling the resulting function. The differences are:
1531
1632
* The result can be called immediately (immune to world age errors)
1733
* The result is not a named generic function, and doesn't participate in
@@ -20,56 +36,59 @@ differences are:
2036
You need to use `RuntimeGeneratedFunctions.init(your_module)` a single time at
2137
the top level of `your_module` before any other uses of the macro.
2238
39+
If provided, `context_module` is module in which symbols within
40+
`function_expression` will be looked up. By default this is module in which
41+
`@RuntimeGeneratedFunction` is expanded.
42+
2343
# Examples
2444
```
2545
RuntimeGeneratedFunctions.init(@__MODULE__) # Required at module top-level
2646
2747
function foo()
2848
expression = :((x,y)->x+y+1) # May be generated dynamically
29-
f = RuntimeGeneratedFunction(@__MODULE__, expression)
49+
f = @RuntimeGeneratedFunction(expression)
3050
f(1,2) # May be called immediately
3151
end
3252
```
3353
"""
34-
struct RuntimeGeneratedFunction{argnames,moduletag,id} <: Function
35-
body::Expr
36-
function RuntimeGeneratedFunction(mod::Module, ex)
37-
if !isdefined(mod, _tagname)
38-
error("""You must use `RuntimeGeneratedFunctions.init(@__MODULE__)` at module
39-
top level before using runtime generated functions""")
40-
end
41-
moduletag = getfield(mod, _tagname)
42-
def = splitdef(ex)
43-
args, body = normalize_args(def[:args]), def[:body]
44-
id = expr_to_id(body)
45-
cached_body = _cache_body(moduletag, id, body)
46-
new{Tuple(args),moduletag,id}(cached_body)
47-
end
54+
macro RuntimeGeneratedFunction(code)
55+
_RGF_constructor_code(:(@__MODULE__), esc(code))
56+
end
57+
macro RuntimeGeneratedFunction(context_module, code)
58+
_RGF_constructor_code(esc(context_module), esc(code))
4859
end
4960

50-
51-
macro RuntimeGeneratedFunction(ex)
52-
Base.depwarn("`@RuntimeGeneratedFunction(ex)` is deprecated, use `RuntimeGeneratedFunction(@__MODULE__, ex)` instead.", :RuntimeGeneratedFunction)
61+
function _RGF_constructor_code(context_module, code)
5362
quote
54-
RuntimeGeneratedFunction(@__MODULE__, $(esc(ex)))
63+
code = $code
64+
cache_module = @__MODULE__
65+
context_module = $context_module
66+
if #==# !isdefined(cache_module, $(QuoteNode(_tagname))) ||
67+
!isdefined(context_module, $(QuoteNode(_tagname)))
68+
init_mods = unique([context_module, cache_module])
69+
error("""You must use `RuntimeGeneratedFunctions.init(@__MODULE__)` at module
70+
top level before using runtime generated functions in $init_mods""")
71+
end
72+
RuntimeGeneratedFunction(cache_module.$_tagname, context_module.$_tagname, $code)
5573
end
5674
end
5775

58-
function Base.show(io::IO, f::RuntimeGeneratedFunction{argnames, moduletag, id}) where {argnames,moduletag,id}
59-
mod = parentmodule(moduletag)
76+
function Base.show(io::IO, ::MIME"text/plain", f::RuntimeGeneratedFunction{argnames, cache_tag, context_tag, id}) where {argnames,cache_tag,context_tag,id}
77+
cache_mod = parentmodule(cache_tag)
78+
context_mod = parentmodule(context_tag)
6079
func_expr = Expr(:->, Expr(:tuple, argnames...), f.body)
61-
print(io, "RuntimeGeneratedFunction(#=in $mod=#, ", repr(func_expr), ")")
80+
print(io, "RuntimeGeneratedFunction(#=in $cache_mod=#, #=using $context_mod=#, ", repr(func_expr), ")")
6281
end
6382

6483
(f::RuntimeGeneratedFunction)(args::Vararg{Any,N}) where N = generated_callfunc(f, args...)
6584

6685
# We'll generate a method of this function in every module which wants to use
67-
# RuntimeGeneratedFunction
86+
# @RuntimeGeneratedFunction
6887
function generated_callfunc end
6988

70-
function generated_callfunc_body(argnames, moduletag, id, __args)
89+
function generated_callfunc_body(argnames, cache_tag, id, __args)
7190
setup = (:($(argnames[i]) = @inbounds __args[$i]) for i in 1:length(argnames))
72-
body = _lookup_body(moduletag, id)
91+
body = _lookup_body(cache_tag, id)
7392
@assert body !== nothing
7493
quote
7594
$(setup...)
@@ -99,9 +118,9 @@ _cache_lock = Threads.SpinLock()
99118
_cachename = Symbol("#_RuntimeGeneratedFunctions_cache")
100119
_tagname = Symbol("#_RGF_ModTag")
101120

102-
function _cache_body(moduletag, id, body)
121+
function _cache_body(cache_tag, id, body)
103122
lock(_cache_lock) do
104-
cache = getfield(parentmodule(moduletag), _cachename)
123+
cache = getfield(parentmodule(cache_tag), _cachename)
105124
# Caching is tricky when `id` is the same for different AST instances:
106125
#
107126
# Tricky case #1: If a function body with the same `id` was cached
@@ -123,9 +142,9 @@ function _cache_body(moduletag, id, body)
123142
end
124143
end
125144

126-
function _lookup_body(moduletag, id)
145+
function _lookup_body(cache_tag, id)
127146
lock(_cache_lock) do
128-
cache = getfield(parentmodule(moduletag), _cachename)
147+
cache = getfield(parentmodule(cache_tag), _cachename)
129148
cache[id].value
130149
end
131150
end
@@ -134,7 +153,7 @@ end
134153
RuntimeGeneratedFunctions.init(mod)
135154
136155
Use this at top level to set up your module `mod` before using
137-
`RuntimeGeneratedFunction(mod, ...)`.
156+
`@RuntimeGeneratedFunction`.
138157
"""
139158
function init(mod)
140159
lock(_cache_lock) do
@@ -155,8 +174,9 @@ function init(mod)
155174
# or so. See:
156175
# https://github.com/JuliaLang/julia/pull/32902
157176
# https://github.com/NHDaly/StagedFunctions.jl/blob/master/src/StagedFunctions.jl#L30
158-
@inline @generated function $RuntimeGeneratedFunctions.generated_callfunc(f::$RuntimeGeneratedFunctions.RuntimeGeneratedFunction{argnames, $_tagname, id}, __args...) where {argnames,id}
159-
$RuntimeGeneratedFunctions.generated_callfunc_body(argnames, $_tagname, id, __args)
177+
@inline @generated function $RuntimeGeneratedFunctions.generated_callfunc(
178+
f::$RuntimeGeneratedFunctions.RuntimeGeneratedFunction{argnames, cache_tag, $_tagname, id}, __args...) where {argnames, cache_tag, id}
179+
$RuntimeGeneratedFunctions.generated_callfunc_body(argnames, cache_tag, id, __args)
160180
end
161181
end)
162182
end

test/precomp/RGFPrecompTest.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ module RGFPrecompTest
22
using RuntimeGeneratedFunctions
33
RuntimeGeneratedFunctions.init(@__MODULE__)
44

5-
f = RuntimeGeneratedFunction(@__MODULE__, :((x,y)->x+y))
5+
f = @RuntimeGeneratedFunction(:((x,y)->x+y))
66
end

test/runtests.jl

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ ex3 = :(function (_du::T,_u::Vector{E},_p::P,_t::Any) where {T<:Vector,E,P}
2727
nothing
2828
end)
2929

30-
f1 = RuntimeGeneratedFunction(@__MODULE__, ex1)
31-
f2 = RuntimeGeneratedFunction(@__MODULE__, ex2)
32-
f3 = RuntimeGeneratedFunction(@__MODULE__, ex3)
30+
f1 = @RuntimeGeneratedFunction(ex1)
31+
f2 = @RuntimeGeneratedFunction(ex2)
32+
f3 = @RuntimeGeneratedFunction(ex3)
3333

3434
@test f1 isa Function
3535

@@ -62,7 +62,7 @@ function no_worldage()
6262
@inbounds _du[2] = _u[2]
6363
nothing
6464
end)
65-
f1 = RuntimeGeneratedFunction(@__MODULE__, ex)
65+
f1 = @RuntimeGeneratedFunction(ex)
6666
du = rand(2)
6767
u = rand(2)
6868
p = nothing
@@ -72,9 +72,9 @@ end
7272
@test no_worldage() === nothing
7373

7474
# Test show()
75-
@test sprint(show, RuntimeGeneratedFunction(@__MODULE__, Base.remove_linenums!(:((x,y)->x+y+1)))) ==
75+
@test sprint(show, MIME"text/plain"(), @RuntimeGeneratedFunction(Base.remove_linenums!(:((x,y)->x+y+1)))) ==
7676
"""
77-
RuntimeGeneratedFunction(#=in $(@__MODULE__)=#, :((x, y)->begin
77+
RuntimeGeneratedFunction(#=in $(@__MODULE__)=#, #=using $(@__MODULE__)=#, :((x, y)->begin
7878
x + y + 1
7979
end))"""
8080

@@ -86,9 +86,9 @@ using RGFPrecompTest
8686

8787
# Test that RuntimeGeneratedFunction with identical body expressions (but
8888
# allocated separately) don't clobber each other when one is GC'd.
89-
f_gc = RuntimeGeneratedFunction(@__MODULE__, Base.remove_linenums!(:((x,y)->x+y+100001)))
89+
f_gc = @RuntimeGeneratedFunction(Base.remove_linenums!(:((x,y)->x+y+100001)))
9090
let
91-
RuntimeGeneratedFunction(@__MODULE__, Base.remove_linenums!(:((x,y)->x+y+100001)))
91+
@RuntimeGeneratedFunction(Base.remove_linenums!(:((x,y)->x+y+100001)))
9292
end
9393
GC.gc()
9494
@test f_gc(1,-1) == 100001
@@ -100,7 +100,7 @@ for k=1:4
100100
t = Threads.@spawn begin
101101
r = Bool[]
102102
for i=1:100
103-
f = RuntimeGeneratedFunction(@__MODULE__, Base.remove_linenums!(:((x,y)->x+y+$i*$k)))
103+
f = @RuntimeGeneratedFunction(Base.remove_linenums!(:((x,y)->x+y+$i*$k)))
104104
x = 1; y = 2;
105105
push!(r, f(x,y) == x + y + i*k)
106106
end
@@ -118,14 +118,17 @@ module GlobalsTest
118118
using RuntimeGeneratedFunctions
119119
RuntimeGeneratedFunctions.init(@__MODULE__)
120120

121-
y = 40
122-
f = RuntimeGeneratedFunction(@__MODULE__, :(x->x+y))
121+
y_in_GlobalsTest = 40
122+
f = @RuntimeGeneratedFunction(:(x->x + y_in_GlobalsTest))
123123
end
124124

125125
@test GlobalsTest.f(2) == 42
126126

127+
f_outside = @RuntimeGeneratedFunction(GlobalsTest, :(x->x + y_in_GlobalsTest))
128+
@test f_outside(2) == 42
129+
127130
@test_throws ErrorException @eval(module NotInitTest
128131
using RuntimeGeneratedFunctions
129132
# RuntimeGeneratedFunctions.init(@__MODULE__) # <-- missing
130-
f = RuntimeGeneratedFunction(@__MODULE__, :(x->x+y))
133+
f = @RuntimeGeneratedFunction(:(x->x+y))
131134
end)

0 commit comments

Comments
 (0)