Skip to content

Commit ffa67cc

Browse files
committed
Fix closure type naming to use list of nested functions
This greatly improves the naming stability over using a global gensym(), allowing our closure tests to be stable. This follow the naming scheme from JuliaLang/julia#53719 with a fixes for closures nested in non-function scopes.
1 parent d955016 commit ffa67cc

File tree

4 files changed

+141
-27
lines changed

4 files changed

+141
-27
lines changed

src/closure_conversion.jl

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function get_box_contents(ctx::ClosureConversionCtx, var, box_ex)
5454
# to fold away the extraneous null check
5555
#
5656
# TODO: Ideally the runtime would rely on provenance info for
57-
# this error and we can remove isdefined check.
57+
# this error and we can remove the isdefined check.
5858
[K"if" [K"call"
5959
"isdefined"::K"core"
6060
box
@@ -230,8 +230,21 @@ function closure_type_fields(ctx, srcref, closure_binds)
230230
return field_syms, field_orig_bindings, field_name_inds
231231
end
232232

233+
function closure_name(mod, name_stack)
234+
basename = "#$(join(name_stack, "#"))##"
235+
i = 0
236+
while true
237+
name = "$basename$i"
238+
if reserve_module_binding(mod, Symbol(name))
239+
return name
240+
end
241+
i += 1
242+
end
243+
end
244+
233245
# Return a thunk which creates a new type for a closure with `field_syms` named
234-
# fields. The new type will be named `name_str`, which must be unique.
246+
# fields. The new type will be named `name_str` which must be an unassigned
247+
# name in the module.
235248
function type_for_closure(ctx::ClosureConversionCtx, srcref, name_str, field_syms)
236249
# New closure types always belong to the module we're expanding into - they
237250
# need to be serialized there during precompile.
@@ -329,10 +342,10 @@ function _convert_closures(ctx::ClosureConversionCtx, ex)
329342
closure_info = get(ctx.closure_infos, func_name_id, nothing)
330343
needs_def = isnothing(closure_info)
331344
if needs_def
332-
# TODO: Names for closures without relying on gensym
333-
name_str = string(gensym("closure"))
345+
closure_binds = ctx.closure_bindings[func_name_id]
334346
field_syms, field_orig_bindings, field_name_inds =
335-
closure_type_fields(ctx, ex, ctx.closure_bindings[func_name_id])
347+
closure_type_fields(ctx, ex, closure_binds)
348+
name_str = closure_name(ctx.mod, closure_binds.name_stack)
336349
closure_type_def, closure_type =
337350
type_for_closure(ctx, ex, name_str, field_syms)
338351
push!(ctx.toplevel_stmts, closure_type_def)

src/runtime.jl

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,8 @@ end
221221
# Get the binding for `name` if one is already resolved in module `mod`. Note
222222
# that we cannot use `isdefined(::Module, ::Symbol)` here, because that causes
223223
# binding resolution which is a massive side effect we must avoid in lowering.
224-
function _get_module_binding(mod, name)
225-
b = @ccall jl_get_module_binding(mod::Module, name::Symbol, 0::Cint)::Ptr{Core.Binding}
224+
function _get_module_binding(mod, name; create=false)
225+
b = @ccall jl_get_module_binding(mod::Module, name::Symbol, create::Cint)::Ptr{Core.Binding}
226226
b == C_NULL ? nothing : unsafe_pointer_to_objref(b)
227227
end
228228

@@ -245,6 +245,20 @@ function is_defined_nothrow_global(mod, name)
245245
isdefined(b.owner, :value)
246246
end
247247

248+
# "Reserve" a binding: create the binding if it doesn't exist but do not assign
249+
# to it.
250+
function reserve_module_binding(mod, name)
251+
# TODO: Fix the race condition here: We should really hold the Module's
252+
# binding lock during this test-and-set type operation. But the binding
253+
# lock is only accessible from C. See also the C code in
254+
# `fl_module_unique_name`.
255+
if _get_module_binding(mod, name; create=false) === nothing
256+
_get_module_binding(mod, name; create=true) !== nothing
257+
else
258+
return false
259+
end
260+
end
261+
248262
#-------------------------------------------------------------------------------
249263
# The following are versions of macros from Base which act as "standard syntax
250264
# extensions" with special semantics known to lowering.

src/scope_analysis.jl

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,11 @@ function update_lambda_binding!(ctx::AbstractLoweringContext, id; kws...)
136136
end
137137

138138
struct ClosureBindings
139-
lambdas::Vector{LambdaBindings}
139+
name_stack::Vector{String} # Names of functions the closure is nested within
140+
lambdas::Vector{LambdaBindings} # Bindings for each method of the closure
140141
end
141142

142-
ClosureBindings() = ClosureBindings(Vector{LambdaBindings}())
143+
ClosureBindings(name_stack) = ClosureBindings(name_stack, Vector{LambdaBindings}())
143144

144145
struct ScopeInfo
145146
# True if scope is the global top level scope
@@ -173,7 +174,8 @@ struct ScopeResolutionContext{GraphType} <: AbstractLoweringContext
173174
# Variables which were implicitly global due to being assigned to in top
174175
# level code
175176
implicit_toplevel_globals::Set{NameKey}
176-
#
177+
# Collection of information about each closure, principally which methods
178+
# are part of the closure (and hence captures).
177179
closure_bindings::Dict{IdTag,ClosureBindings}
178180
end
179181

@@ -554,7 +556,13 @@ function _resolve_scopes(ctx, ex::SyntaxTree)
554556
func_name_id = func_name.var_id
555557
if lookup_binding(ctx, func_name_id).kind == :local
556558
cbinds = get!(ctx.closure_bindings, func_name_id) do
557-
ClosureBindings()
559+
name_stack = Vector{String}()
560+
for fname in ctx.method_def_stack
561+
if kind(fname) == K"BindingId"
562+
push!(name_stack, lookup_binding(ctx, fname).name)
563+
end
564+
end
565+
ClosureBindings(name_stack)
558566
end
559567
push!(cbinds.lambdas, lambda_bindings)
560568
end

test/closures_ir.jl

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@ let
77
end
88
end
99
#---------------------
10-
1 (newvar slot)
11-
2 (= slot₂ (call core.Box))
10+
1 (= slot₂ (call core.Box))
11+
2 (newvar slot)
1212
3 --- thunk
13-
1 (global TestMod.##closure#277)
13+
1 (global TestMod.#f##0)
1414
2 (call core.svec)
1515
3 (call core.svec :x)
1616
4 (call core.svec)
17-
5 (call core._structtype TestMod :##closure#277 %₂ %₃ %₄ false 1)
17+
5 (call core._structtype TestMod :#f##0 %₂ %₃ %₄ false 1)
1818
6 (call core._setsuper! %₅ core.Function)
19-
7 (const TestMod.##closure#277)
20-
8 (= TestMod.##closure#277 %₅)
19+
7 (const TestMod.#f##0)
20+
8 (= TestMod.#f##0 %₅)
2121
9 (call core.svec core.Box)
2222
10 (call core._typebody! %%₉)
2323
11 (return core.nothing)
24-
4 TestMod.##closure#277
24+
4 TestMod.#f##0
2525
5 (call core.svec %₄ core.Any)
2626
6 (call core.svec)
2727
7 (call core.svec %%₆ :($(QuoteNode(:(#= line 3 =#)))))
@@ -39,7 +39,7 @@ end
3939
9 1
4040
10 slot₂/x
4141
11 (call core.setfield! %₁₀ :contents %₉)
42-
12 TestMod.##closure#277
42+
12 TestMod.#f##0
4343
13 slot₂/f
4444
14 (= slot₁/f (new %₁₂ %₁₃))
4545
15 slot₁/f
@@ -55,21 +55,21 @@ let
5555
end
5656
end
5757
#---------------------
58-
1 (newvar slot)
59-
2 (= slot₂ (call core.Box))
58+
1 (= slot₂ (call core.Box))
59+
2 (newvar slot)
6060
3 --- thunk
61-
1 (global TestMod.##closure#278)
61+
1 (global TestMod.#f##1)
6262
2 (call core.svec)
6363
3 (call core.svec :x)
6464
4 (call core.svec)
65-
5 (call core._structtype TestMod :##closure#278 %₂ %₃ %₄ false 1)
65+
5 (call core._structtype TestMod :#f##1 %₂ %₃ %₄ false 1)
6666
6 (call core._setsuper! %₅ core.Function)
67-
7 (const TestMod.##closure#278)
68-
8 (= TestMod.##closure#278 %₅)
67+
7 (const TestMod.#f##1)
68+
8 (= TestMod.#f##1 %₅)
6969
9 (call core.svec core.Box)
7070
10 (call core._typebody! %%₉)
7171
11 (return core.nothing)
72-
4 TestMod.##closure#278
72+
4 TestMod.#f##1
7373
5 (call core.svec %₄ core.Any)
7474
6 (call core.svec)
7575
7 (call core.svec %%₆ :($(QuoteNode(:(#= line 3 =#)))))
@@ -81,9 +81,88 @@ end
8181
9 1
8282
10 slot₂/x
8383
11 (call core.setfield! %₁₀ :contents %₉)
84-
12 TestMod.##closure#278
84+
12 TestMod.#f##1
8585
13 slot₂/f
8686
14 (= slot₁/f (new %₁₂ %₁₃))
8787
15 slot₁/f
8888
16 slot₁/f
8989
17 (return %₁₆)
90+
91+
########################################
92+
# Function where arguments are captured into a closure
93+
function f(x)
94+
function g()
95+
x = 10
96+
end
97+
g()
98+
x
99+
end
100+
#---------------------
101+
1 (method TestMod.f)
102+
2 --- thunk
103+
1 (global TestMod.#f#g##0)
104+
2 (call core.svec)
105+
3 (call core.svec :x)
106+
4 (call core.svec)
107+
5 (call core._structtype TestMod :#f#g##0 %₂ %₃ %₄ false 1)
108+
6 (call core._setsuper! %₅ core.Function)
109+
7 (const TestMod.#f#g##0)
110+
8 (= TestMod.#f#g##0 %₅)
111+
9 (call core.svec core.Box)
112+
10 (call core._typebody! %%₉)
113+
11 (return core.nothing)
114+
3 TestMod.#f#g##0
115+
4 (call core.svec %₃)
116+
5 (call core.svec)
117+
6 (call core.svec %%₅ :($(QuoteNode(:(#= line 2 =#)))))
118+
7 --- method core.nothing %
119+
1 10
120+
2 (call core.getfield slot₁/x :x)
121+
3 (call core.setfield! %:contents %₁)
122+
4 (return %₁)
123+
8 TestMod.f
124+
9 (call core.Typeof %₈)
125+
10 (call core.svec %₉ core.Any)
126+
11 (call core.svec)
127+
12 (call core.svec %₁₀ %₁₁ :($(QuoteNode(:(#= line 1 =#)))))
128+
13 --- method core.nothing %₁₂
129+
1 (= slot₂/x (call core.Box slot₂/x))
130+
2 slot₂/x
131+
3 (call core.isdefined %:contents)
132+
4 (gotoifnot %₃ label₆)
133+
5 (goto label₈)
134+
6 (newvar slot₅/x)
135+
7 slot₅/x
136+
8 (call core.getfield %:contents)
137+
9 (call core.Box %₈)
138+
10 (call core.setfield! slot₂/x :contents %₉)
139+
11 (newvar slot₃)
140+
12 TestMod.#f#g##0
141+
13 slot₂/g
142+
14 (call core.isdefined %₁₃ :contents)
143+
15 (gotoifnot %₁₄ label₁₇)
144+
16 (goto label₁₉)
145+
17 (newvar slot₆/x)
146+
18 slot₆/x
147+
19 (call core.getfield %₁₃ :contents)
148+
20 (= slot₃/g (new %₁₂ %₁₉))
149+
21 slot₃/g
150+
22 slot₃/g
151+
23 slot₃/g
152+
24 (call %₂₃)
153+
25 slot₂/x
154+
26 (call core.isdefined %₂₅ :contents)
155+
27 (gotoifnot %₂₆ label₂₉)
156+
28 (goto label₃₁)
157+
29 (newvar slot₇/x)
158+
30 slot₇/x
159+
31 (call core.getfield %₂₅ :contents)
160+
32 (call core.isdefined %₃₁ :contents)
161+
33 (gotoifnot %₃₂ label₃₅)
162+
34 (goto label₃₇)
163+
35 (newvar slot₄/x)
164+
36 slot₄/x
165+
37 (call core.getfield %₃₁ :contents)
166+
38 (return %₃₇)
167+
14 (return %₁₂)
168+

0 commit comments

Comments
 (0)