@@ -10,15 +10,14 @@ export @RuntimeGeneratedFunction
1010
1111This type should be constructed via the macro @RuntimeGeneratedFunction.
1212"""
13- mutable struct RuntimeGeneratedFunction{moduletag,id,argnames}
13+ struct RuntimeGeneratedFunction{moduletag,id,argnames}
1414 body:: Expr
1515 function RuntimeGeneratedFunction (moduletag, ex)
16- id = expr2bytes (ex)
1716 def = splitdef (ex)
1817 args, body = normalize_args (def[:args ]), def[:body ]
19- f = new {moduletag,id,Tuple(args)} (body)
20- _cache_self (moduletag, id, f )
21- return f
18+ id = expr2bytes (body)
19+ cached_body = _cache_body (moduletag, id, body )
20+ new {moduletag,id,Tuple(args)} (cached_body)
2221 end
2322end
2423
6261
6362@inline @generated function generated_callfunc (f:: RuntimeGeneratedFunction{moduletag, id, argnames} , __args... ) where {moduletag,id,argnames}
6463 setup = (:($ (argnames[i]) = @inbounds __args[$ i]) for i in 1 : length (argnames))
65- f_value = _lookup_self (moduletag, id)
64+ body = _lookup_body (moduletag, id)
65+ @assert body != = nothing
6666 quote
6767 $ (setup... )
68- $ (f_value . body)
68+ $ (body)
6969 end
7070end
7171
72- # ## Function caching and lookup
72+ # ## Body caching and lookup
7373#
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.
74+ # Looking up the body of a RuntimeGeneratedFunction based on the id is a little
75+ # complicated because we want the `id=>body` mapping to survive precompilation.
76+ # This means we need to store the mapping created by a module in that module
77+ # itself.
7778#
7879# For that, we need a way to lookup the correct module from an instance of
7980# RuntimeGeneratedFunction. Modules can't be type parameters, but we can use
8687_cachename = Symbol (" #_RuntimeGeneratedFunctions_cache" )
8788_tagname = Symbol (" #_RuntimeGeneratedFunctions_ModTag" )
8889
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)
90+ function _cache_body (moduletag, id, body)
91+ cache = getfield (parentmodule (moduletag), _cachename)
92+ # Caching is tricky when `id` is the same for different AST instances:
93+ #
94+ # Tricky case #1: If a function body with the same `id` was cached
95+ # previously, we need to use that older instance of the body AST as the
96+ # canonical one rather than `body`. This ensures the lifetime of the
97+ # body in the cache will always cover the lifetime of the parent
98+ # `RuntimeGeneratedFunction`s when they share the same `id`.
99+ #
100+ # Tricky case #2: Unless we hold a separate reference to
101+ # `cache[id].value`, the GC can collect it (causing it to become
102+ # `nothing`). So root it in a local variable first.
103+ #
104+ cached_body = haskey (cache, id) ? cache[id]. value : nothing
105+ cached_body = cached_body != = nothing ? cached_body : body
106+ # Use a WeakRef to allow `body` to be garbage collected. (After GC, the
107+ # cache will still contain an empty entry with key `id`.)
108+ cache[id] = WeakRef (cached_body)
109+ return cached_body
93110end
94111
95- function _lookup_self (moduletag, id)
112+ function _lookup_body (moduletag, id)
96113 getfield (parentmodule (moduletag), _cachename)[id]. value
97114end
98115
0 commit comments