Skip to content

Commit 2dd4778

Browse files
Merge pull request #32 from simeonschaub/sds/opaque
support for closures via opaque closures
2 parents 65d2451 + 2e6151e commit 2dd4778

File tree

3 files changed

+114
-10
lines changed

3 files changed

+114
-10
lines changed

.github/workflows/CI.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,24 @@ on:
88
- master
99
jobs:
1010
test:
11-
runs-on: ubuntu-latest
11+
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
12+
runs-on: ${{ matrix.os }}
13+
strategy:
14+
matrix:
15+
version:
16+
- '1'
17+
- 'nightly'
18+
os:
19+
- ubuntu-latest
20+
arch:
21+
- x64
22+
fail-fast: false
1223
steps:
1324
- uses: actions/checkout@v2
1425
- uses: julia-actions/setup-julia@v1
1526
with:
16-
version: 1
27+
version: ${{ matrix.version }}
28+
arch: ${{ matrix.arch }}
1729
- uses: actions/cache@v1
1830
env:
1931
cache-name: cache-artifacts

src/RuntimeGeneratedFunctions.jl

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ export RuntimeGeneratedFunction, @RuntimeGeneratedFunction
77

88
"""
99
@RuntimeGeneratedFunction(function_expression)
10-
@RuntimeGeneratedFunction(context_module, function_expression)
10+
@RuntimeGeneratedFunction(context_module, function_expression, opaque_closures=true)
1111
12-
RuntimeGeneratedFunction(cache_module, context_module, function_expression)
12+
RuntimeGeneratedFunction(cache_module, context_module, function_expression; opaque_closures=true)
1313
1414
Construct a function from `function_expression` which can be called immediately
1515
without world age problems. Somewhat like using `eval(function_expression)` and
@@ -31,6 +31,13 @@ If provided, `context_module` is module in which symbols within
3131
which is currently being precompiled. Normally this would be set to
3232
`@__MODULE__` using one of the macro constructors.
3333
34+
If `opaque_closures` is `true`, all closures in `function_expression` are
35+
converted to
36+
[opaque closures](https://github.com/JuliaLang/julia/pull/37849#issue-496641229).
37+
This allows for the use of closures and generators inside the generated function,
38+
but may not work in all cases due to slightly different semantics. This feature
39+
requires Julia 1.7.
40+
3441
# Examples
3542
```
3643
RuntimeGeneratedFunctions.init(@__MODULE__) # Required at module top-level
@@ -44,9 +51,13 @@ end
4451
"""
4552
struct RuntimeGeneratedFunction{argnames, cache_tag, context_tag, id} <: Function
4653
body::Expr
47-
function RuntimeGeneratedFunction(cache_tag, context_tag, ex)
54+
function RuntimeGeneratedFunction(cache_tag, context_tag, ex; opaque_closures=true)
4855
def = splitdef(ex)
4956
args, body = normalize_args(def[:args]), def[:body]
57+
if opaque_closures && isdefined(Base, :Experimental) &&
58+
isdefined(Base.Experimental, Symbol("@opaque"))
59+
body = closures_to_opaque(body)
60+
end
5061
id = expr_to_id(body)
5162
cached_body = _cache_body(cache_tag, id, body)
5263
new{Tuple(args), cache_tag, context_tag, id}(cached_body)
@@ -62,20 +73,29 @@ function _check_rgf_initialized(mods...)
6273
end
6374
end
6475

65-
function RuntimeGeneratedFunction(cache_module::Module, context_module::Module, code)
76+
function RuntimeGeneratedFunction(
77+
cache_module::Module, context_module::Module, code; opaque_closures=true,
78+
)
6679
_check_rgf_initialized(cache_module, context_module)
67-
RuntimeGeneratedFunction(getfield(cache_module, _tagname),
68-
getfield(context_module, _tagname), code)
80+
RuntimeGeneratedFunction(
81+
getfield(cache_module, _tagname),
82+
getfield(context_module, _tagname),
83+
code;
84+
opaque_closures = opaque_closures
85+
)
6986
end
7087

7188
macro RuntimeGeneratedFunction(code)
7289
quote
7390
RuntimeGeneratedFunction(@__MODULE__, @__MODULE__, $(esc(code)))
7491
end
7592
end
76-
macro RuntimeGeneratedFunction(context_module, code)
93+
macro RuntimeGeneratedFunction(context_module, code, opaque_closures=true)
7794
quote
78-
RuntimeGeneratedFunction(@__MODULE__, $(esc(context_module)), $(esc(code)))
95+
RuntimeGeneratedFunction(
96+
@__MODULE__, $(esc(context_module)), $(esc(code));
97+
opaque_closures = $(esc(opaque_closures)),
98+
)
7999
end
80100
end
81101

@@ -208,4 +228,51 @@ function expr_to_id(ex)
208228
return Tuple(reinterpret(UInt32, sha1(take!(io))))
209229
end
210230

231+
@nospecialize
232+
233+
closures_to_opaque(x, _=nothing) = x
234+
_tconvert(T, x) = Expr(:(::), Expr(:call, GlobalRef(Base, :convert), T, x), T)
235+
function closures_to_opaque(ex::Expr, return_type=nothing)
236+
head, args = ex.head, ex.args
237+
fdef = splitdef(ex; throw=false)
238+
if fdef !== nothing
239+
body = get(fdef, :body, nothing)
240+
if haskey(fdef, :rtype)
241+
body = _tconvert(fdef[:rtype], closures_to_opaque(body, fdef[:rtype]))
242+
delete!(fdef, :rtype)
243+
else
244+
body = closures_to_opaque(body)
245+
end
246+
fdef[:head] = :(->)
247+
fdef[:body] = body
248+
name = get(fdef, :name, nothing)
249+
name !== nothing && delete!(fdef, :name)
250+
_ex = Expr(:opaque_closure, combinedef(fdef))
251+
# TODO: emit named opaque closure for better stacktraces
252+
# (ref https://github.com/JuliaLang/julia/pull/40242)
253+
if name !== nothing
254+
name isa Symbol ||
255+
error("Unsupported function definition `$ex` in RuntimeGeneratedFunction.")
256+
_ex = Expr(:(=), name, _ex)
257+
end
258+
return _ex
259+
elseif head === :generator
260+
f_args = Expr(:tuple, Any[x.args[1] for x in args[2:end]]...)
261+
iters = Any[x.args[2] for x in args[2:end]]
262+
return Expr(
263+
:call,
264+
GlobalRef(Base, :Generator),
265+
closures_to_opaque(Expr(:(->), f_args, args[1])),
266+
iters...,
267+
)
268+
elseif head === :opaque_closure
269+
return closures_to_opaque(args[1])
270+
elseif head === :return && return_type !== nothing
271+
return Expr(:return, _tconvert(return_type, closures_to_opaque(args[1], return_type)))
272+
end
273+
return Expr(head, Any[closures_to_opaque(x, return_type) for x in args]...)
274+
end
275+
276+
@specialize
277+
211278
end

test/runtests.jl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,28 @@ f_outside = @RuntimeGeneratedFunction(GlobalsTest, :(x->x + y_in_GlobalsTest))
133133
# RuntimeGeneratedFunctions.init(@__MODULE__) # <-- missing
134134
f = @RuntimeGeneratedFunction(:(x->x+y))
135135
end)
136+
137+
# closures
138+
if VERSION >= v"1.7.0-DEV.351"
139+
ex = :(x -> (y -> x + y))
140+
@test @RuntimeGeneratedFunction(ex)(2)(3) === 5
141+
142+
ex = :(x -> (f(y::Int)::Float64 = x + y; f))
143+
@test @RuntimeGeneratedFunction(ex)(2)(3) === 5.0
144+
145+
ex = :(x -> function (y::Int)
146+
return x + y
147+
end)
148+
@test @RuntimeGeneratedFunction(ex)(2)(3) === 5
149+
150+
ex = :(x -> function f(y::Int)::UInt8
151+
return x + y
152+
end)
153+
@test @RuntimeGeneratedFunction(ex)(2)(3) === 0x05
154+
155+
ex = :(x -> sum(i^2 for i in 1:x))
156+
@test @RuntimeGeneratedFunction(ex)(3) === 14
157+
158+
ex = :(x -> [2i for i in 1:x])
159+
@test @RuntimeGeneratedFunction(ex)(3) == [2, 4, 6]
160+
end

0 commit comments

Comments
 (0)