Skip to content

Commit d7b993d

Browse files
authored
Merge pull request #63 from c42f/c42f/re-enable-gc
Re-enable GC of RGFs which haven't been `drop_expr()`d
2 parents 4ffaeb3 + c8bd814 commit d7b993d

File tree

2 files changed

+50
-14
lines changed

2 files changed

+50
-14
lines changed

src/RuntimeGeneratedFunctions.jl

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,19 @@ struct RuntimeGeneratedFunction{argnames, cache_tag, context_tag, id, B} <: Func
7575
end
7676
end
7777

78-
function drop_expr(::RuntimeGeneratedFunction{A, C1, C2, ID}) where {A, C1, C2, ID}
79-
RuntimeGeneratedFunction{A, C1, C2, ID}(nothing)
78+
function drop_expr(::RuntimeGeneratedFunction{a, cache_tag, c, id}) where {a, cache_tag, c,
79+
id}
80+
# When dropping the reference to the body from an RGF, we need to upgrade
81+
# from a weak to a strong reference in the cache to prevent the body being
82+
# GC'd.
83+
lock(_cache_lock) do
84+
cache = getfield(parentmodule(cache_tag), _cachename)
85+
body = cache[id]
86+
if body isa WeakRef
87+
cache[id] = body.value
88+
end
89+
end
90+
RuntimeGeneratedFunction{a, cache_tag, c, id}(nothing)
8091
end
8192

8293
function _check_rgf_initialized(mods...)
@@ -119,7 +130,7 @@ function Base.show(io::IO, ::MIME"text/plain",
119130
}
120131
cache_mod = parentmodule(cache_tag)
121132
context_mod = parentmodule(context_tag)
122-
func_expr = Expr(:->, Expr(:tuple, argnames...), f.body)
133+
func_expr = Expr(:->, Expr(:tuple, argnames...), _lookup_body(cache_tag, id))
123134
print(io, "RuntimeGeneratedFunction(#=in $cache_mod=#, #=using $context_mod=#, ",
124135
repr(func_expr), ")")
125136
end
@@ -169,24 +180,38 @@ function _cache_body(cache_tag, id, body)
169180
cache = getfield(parentmodule(cache_tag), _cachename)
170181
# Caching is tricky when `id` is the same for different AST instances:
171182
#
172-
# Tricky case #1: If a function body with the same `id` was cached
173-
# previously, we need to use that older instance of the body AST as the
174-
# canonical one rather than `body`. This ensures the lifetime of the
175-
# body in the cache will always cover the lifetime of the parent
176-
# `RuntimeGeneratedFunction`s when they share the same `id`.
177-
cached_body = haskey(cache, id) ? cache[id] : nothing
178-
cached_body = cached_body !== nothing ? cached_body : body
179-
# We cannot use WeakRef because we might drop body to make RGF GPU
180-
# compatible.
181-
cache[id] = cached_body
183+
# 1. If a function body with the same `id` was cached previously, we need
184+
# to use that older instance of the body AST as the canonical one
185+
# rather than `body`. This ensures the lifetime of the body in the
186+
# cache will always cover the lifetime of all RGFs which share the same
187+
# `id`.
188+
#
189+
# 2. Unless we hold a separate reference to `cache[id].value`, the GC
190+
# can collect it (causing it to become `nothing`). So root it in a
191+
# local variable first.
192+
#
193+
cached_body = get(cache, id, nothing)
194+
if !isnothing(cached_body)
195+
if cached_body isa WeakRef
196+
# `value` may be nothing here if it was previously cached but GC'd
197+
cached_body = cached_body.value
198+
end
199+
end
200+
if isnothing(cached_body)
201+
cached_body = body
202+
# Use a WeakRef to allow `body` to be garbage collected. (After GC, the
203+
# cache will still contain an empty entry with key `id`.)
204+
cache[id] = WeakRef(cached_body)
205+
end
182206
return cached_body
183207
end
184208
end
185209

186210
function _lookup_body(cache_tag, id)
187211
lock(_cache_lock) do
188212
cache = getfield(parentmodule(cache_tag), _cachename)
189-
cache[id]
213+
body = cache[id]
214+
body isa WeakRef ? body.value : body
190215
end
191216
end
192217

test/runtests.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@ end
9696
GC.gc()
9797
@test f_gc(1, -1) == 100001
9898

99+
# Test that drop_expr works
100+
f_drop1, f_drop2 = let
101+
ex = Base.remove_linenums!(:(x -> x - 1))
102+
# Construct two identical RGFs here to test the cache deduplication code
103+
(drop_expr(@RuntimeGeneratedFunction(ex)),
104+
drop_expr(@RuntimeGeneratedFunction(ex)))
105+
end
106+
GC.gc()
107+
@test f_drop1(1) == 0
108+
@test f_drop2(1) == 0
109+
99110
# Test that threaded use works
100111
tasks = []
101112
for k in 1:4

0 commit comments

Comments
 (0)