@@ -2,27 +2,106 @@ module RuntimeGeneratedFunctions
22
33using ExprTools, Serialization, SHA
44
5- const function_cache = Dict {Tuple,Expr} ()
6- struct RuntimeGeneratedFunction{uuid,argnames}
7- function RuntimeGeneratedFunction (ex)
5+ export @RuntimeGeneratedFunction
6+
7+
8+ """
9+ RuntimeGeneratedFunction
10+
11+ This type should be constructed via the macro @RuntimeGeneratedFunction.
12+ """
13+ struct RuntimeGeneratedFunction{moduletag,id,argnames}
14+ function RuntimeGeneratedFunction (moduletag, ex)
815 def = splitdef (ex)
916 args, body = normalize_args (def[:args ]), def[:body ]
10- uuid = expr2bytes (body)
11- function_cache[uuid] = body
12- new {uuid,Tuple(args)} ()
17+ id = expr2bytes (body)
18+ _cache_body (moduletag, id, body)
19+ new {moduletag,id,Tuple(args)} ()
20+ end
21+ end
22+
23+ """
24+ @RuntimeGeneratedFunction(function_expression)
25+
26+ Construct a function from `function_expression` which can be called immediately
27+ without world age problems. Somewhat like using `eval(function_expression)` and
28+ then calling the resulting function. The differences are:
29+
30+ * The result can be called immediately (immune to world age errors)
31+ * The result is not a named generic function, and doesn't participate in
32+ generic function dispatch; it's more like a callable method.
33+
34+ # Examples
35+ ```
36+ function foo()
37+ expression = :((x,y)->x+y+1) # May be generated dynamically
38+ f = @RuntimeGeneratedFunction(expression)
39+ f(1,2) # May be called immediately
40+ end
41+ ```
42+ """
43+ macro RuntimeGeneratedFunction (ex)
44+ _ensure_cache_exists! (__module__)
45+ quote
46+ RuntimeGeneratedFunction (
47+ $ (esc (_tagname)),
48+ $ (esc (ex))
49+ )
1350 end
1451end
52+
53+ function Base. show (io:: IO , f:: RuntimeGeneratedFunction{moduletag, id, argnames} ) where {moduletag,id,argnames}
54+ body = _lookup_body (moduletag, id)
55+ mod = parentmodule (moduletag)
56+ func_expr = Expr (:-> , Expr (:tuple , argnames... ), body)
57+ print (io, " RuntimeGeneratedFunction(#=in $mod =#, " , repr (func_expr), " )" )
58+ end
59+
1560(f:: RuntimeGeneratedFunction )(args:: Vararg{Any,N} ) where N = generated_callfunc (f, args... )
1661
17- @inline @generated function generated_callfunc (f:: RuntimeGeneratedFunction{uuid, argnames} ,__args... ) where {uuid ,argnames}
62+ @inline @generated function generated_callfunc (f:: RuntimeGeneratedFunction{moduletag, id, argnames} , __args... ) where {moduletag,id ,argnames}
1863 setup = (:($ (argnames[i]) = @inbounds __args[$ i]) for i in 1 : length (argnames))
1964 quote
2065 $ (setup... )
21- $ (function_cache[uuid] )
66+ $ (_lookup_body (moduletag, id) )
2267 end
2368end
2469
25- export RuntimeGeneratedFunction
70+ # ## Function body caching and lookup
71+ #
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.
76+ #
77+ # 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+ # any type which belongs to the module as a proxy "tag" for the module.
80+ #
81+ # (We could even abuse `typeof(__module__.eval)` for the tag, though this is a
82+ # little non-robust to weird special cases like Main.eval being
83+ # Base.MainInclude.eval.)
84+
85+ _cachename = Symbol (" #_RuntimeGeneratedFunctions_cache" )
86+ _tagname = Symbol (" #_RuntimeGeneratedFunctions_ModTag" )
87+
88+ function _cache_body (moduletag, id, body)
89+ getfield (parentmodule (moduletag), _cachename)[id] = body
90+ end
91+
92+ function _lookup_body (moduletag, id)
93+ getfield (parentmodule (moduletag), _cachename)[id]
94+ end
95+
96+ function _ensure_cache_exists! (mod)
97+ if ! isdefined (mod, _cachename)
98+ mod. eval (quote
99+ const $ _cachename = Dict {Tuple,Expr} ()
100+ struct $ _tagname
101+ end
102+ end )
103+ end
104+ end
26105
27106# ##
28107# ## Utilities
0 commit comments