Skip to content

Commit e186934

Browse files
committed
Allow function body ASTs to be GC'd
This moves the function body AST into the RuntimeGeneratedFunction itself, and caches the function object itself, but only holds it in the cache via a WeakRef, thus ensuring that it can be GC'd as normal. (A slight wart with this approach is that the id is still present in the cache after GC has run.) As a side effect, this makes accessing the body in normal non-generated functions a lot neater.
1 parent e51174f commit e186934

File tree

1 file changed

+21
-18
lines changed

1 file changed

+21
-18
lines changed

src/RuntimeGeneratedFunctions.jl

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ export @RuntimeGeneratedFunction
1010
1111
This type should be constructed via the macro @RuntimeGeneratedFunction.
1212
"""
13-
struct RuntimeGeneratedFunction{moduletag,id,argnames}
13+
mutable struct RuntimeGeneratedFunction{moduletag,id,argnames}
14+
body::Expr
1415
function RuntimeGeneratedFunction(moduletag, ex)
16+
id = expr2bytes(ex)
1517
def = splitdef(ex)
1618
args, body = normalize_args(def[:args]), def[:body]
17-
id = expr2bytes(body)
18-
_cache_body(moduletag, id, body)
19-
new{moduletag,id,Tuple(args)}()
19+
f = new{moduletag,id,Tuple(args)}(body)
20+
_cache_self(moduletag, id, f)
21+
return f
2022
end
2123
end
2224

@@ -51,31 +53,30 @@ macro RuntimeGeneratedFunction(ex)
5153
end
5254

5355
function Base.show(io::IO, f::RuntimeGeneratedFunction{moduletag, id, argnames}) where {moduletag,id,argnames}
54-
body = _lookup_body(moduletag, id)
5556
mod = parentmodule(moduletag)
56-
func_expr = Expr(:->, Expr(:tuple, argnames...), body)
57+
func_expr = Expr(:->, Expr(:tuple, argnames...), f.body)
5758
print(io, "RuntimeGeneratedFunction(#=in $mod=#, ", repr(func_expr), ")")
5859
end
5960

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

6263
@inline @generated function generated_callfunc(f::RuntimeGeneratedFunction{moduletag, id, argnames}, __args...) where {moduletag,id,argnames}
6364
setup = (:($(argnames[i]) = @inbounds __args[$i]) for i in 1:length(argnames))
65+
f_value = _lookup_self(moduletag, id)
6466
quote
6567
$(setup...)
66-
$(_lookup_body(moduletag, id))
68+
$(f_value.body)
6769
end
6870
end
6971

70-
### Function body caching and lookup
72+
### Function caching and lookup
7173
#
72-
# Caching the body of a RuntimeGeneratedFunction is a little complicated
73-
# because we want the `id=>body` mapping to survive precompilation. This means
74-
# we need to store the cache of mappings which are created by a module in that
75-
# module itself.
74+
# Looking up a RuntimeGeneratedFunction based on the id is a little complicated
75+
# because we want the `id=>func` mapping to survive precompilation. This means
76+
# we need to store the mapping created by a module in that module itself.
7677
#
7778
# For that, we need a way to lookup the correct module from an instance of
78-
# RuntimeGeneratedFunction. Modules can't be type parameters, but we can use
79+
# RuntimeGeneratedFunction. Modules can't be type parameters, but we can use
7980
# any type which belongs to the module as a proxy "tag" for the module.
8081
#
8182
# (We could even abuse `typeof(__module__.eval)` for the tag, though this is a
@@ -85,18 +86,20 @@ end
8586
_cachename = Symbol("#_RuntimeGeneratedFunctions_cache")
8687
_tagname = Symbol("#_RuntimeGeneratedFunctions_ModTag")
8788

88-
function _cache_body(moduletag, id, body)
89-
getfield(parentmodule(moduletag), _cachename)[id] = body
89+
function _cache_self(moduletag, id, f)
90+
# Use a WeakRef to allow `f` to be garbage collected. (After GC the cache
91+
# will still contain an empty entry with key `id`.)
92+
getfield(parentmodule(moduletag), _cachename)[id] = WeakRef(f)
9093
end
9194

92-
function _lookup_body(moduletag, id)
93-
getfield(parentmodule(moduletag), _cachename)[id]
95+
function _lookup_self(moduletag, id)
96+
getfield(parentmodule(moduletag), _cachename)[id].value
9497
end
9598

9699
function _ensure_cache_exists!(mod)
97100
if !isdefined(mod, _cachename)
98101
mod.eval(quote
99-
const $_cachename = Dict{Tuple,Expr}()
102+
const $_cachename = Dict()
100103
struct $_tagname
101104
end
102105
end)

0 commit comments

Comments
 (0)