Skip to content

Commit ef9bd02

Browse files
authored
World age fixes for macro expansion and generated functions (#63)
* Support for expanding macros in a specific world age * Connect macro expansion world to Core hook * Fixes for CodeInfo generation in GeneratedFunctionStub - ensure `isva` and edges to bindings are set as is done in Base. * Test non-generated branch of generated function
1 parent 858e324 commit ef9bd02

File tree

8 files changed

+88
-40
lines changed

8 files changed

+88
-40
lines changed

src/eval.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
function lower(mod::Module, ex0, expr_compat_mode=false)
2-
ctx1, ex1 = expand_forms_1( mod, ex0, expr_compat_mode)
1+
function lower(mod::Module, ex0; expr_compat_mode=false, world=Base.get_world_counter())
2+
ctx1, ex1 = expand_forms_1( mod, ex0, expr_compat_mode, world)
33
ctx2, ex2 = expand_forms_2( ctx1, ex1)
44
ctx3, ex3 = resolve_scopes( ctx2, ex2)
55
ctx4, ex4 = convert_closures(ctx3, ex3)
66
ctx5, ex5 = linearize_ir( ctx4, ex4)
77
ex5
88
end
99

10-
function macroexpand(mod::Module, ex, expr_compat_mode=false)
11-
ctx1, ex1 = expand_forms_1(mod, ex, expr_compat_mode)
10+
function macroexpand(mod::Module, ex; expr_compat_mode=false, world=Base.get_world_counter())
11+
ctx1, ex1 = expand_forms_1(mod, ex, expr_compat_mode, world)
1212
ex1
1313
end
1414

@@ -354,7 +354,7 @@ function Core.eval(mod::Module, ex::SyntaxTree; expr_compat_mode::Bool=false)
354354
end
355355
return x
356356
end
357-
linear_ir = lower(mod, ex, expr_compat_mode)
357+
linear_ir = lower(mod, ex; expr_compat_mode)
358358
expr_form = to_lowered_expr(mod, linear_ir, 0)
359359
eval(mod, expr_form)
360360
end

src/hooks.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ function core_lowering_hook(@nospecialize(code), mod::Module,
1717

1818
st0 = code isa Expr ? expr_to_syntaxtree(code, LineNumberNode(line, file)) : code
1919
try
20-
ctx1, st1 = expand_forms_1( mod, st0, true)
20+
ctx1, st1 = expand_forms_1( mod, st0, true, world)
2121
ctx2, st2 = expand_forms_2( ctx1, st1)
2222
ctx3, st3 = resolve_scopes( ctx2, st2)
2323
ctx4, st4 = convert_closures(ctx3, st3)

src/macro_expansion.jl

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ struct MacroExpansionContext{GraphType} <: AbstractLoweringContext
2121
scope_layers::Vector{ScopeLayer}
2222
scope_layer_stack::Vector{LayerId}
2323
expr_compat_mode::Bool
24+
macro_world::UInt
2425
end
2526

26-
function MacroExpansionContext(graph::SyntaxGraph, mod::Module, expr_compat_mode::Bool)
27+
function MacroExpansionContext(graph::SyntaxGraph, mod::Module, expr_compat_mode::Bool, world::UInt)
2728
layers = ScopeLayer[ScopeLayer(1, mod, 0, false)]
28-
MacroExpansionContext(graph, Bindings(), layers, LayerId[length(layers)], expr_compat_mode)
29+
MacroExpansionContext(graph, Bindings(), layers, LayerId[length(layers)], expr_compat_mode, world)
2930
end
3031

3132
current_layer(ctx::MacroExpansionContext) = ctx.scope_layers[last(ctx.scope_layer_stack)]
@@ -217,11 +218,10 @@ function expand_macro(ctx, ex)
217218
# age changes concurrently.
218219
#
219220
# TODO: Allow this to be passed in
220-
macro_world = Base.get_world_counter()
221-
if hasmethod(macfunc, Tuple{typeof(mctx), typeof.(raw_args)...}; world=macro_world)
221+
if hasmethod(macfunc, Tuple{typeof(mctx), typeof.(raw_args)...}; world=ctx.macro_world)
222222
macro_args = prepare_macro_args(ctx, mctx, raw_args)
223223
expanded = try
224-
Base.invoke_in_world(macro_world, macfunc, macro_args...)
224+
Base.invoke_in_world(ctx.macro_world, macfunc, macro_args...)
225225
catch exc
226226
newexc = exc isa MacroExpansionError ?
227227
MacroExpansionError(mctx, exc.ex, exc.msg, exc.position, exc.err) :
@@ -260,14 +260,14 @@ function expand_macro(ctx, ex)
260260
push!(macro_args, Expr(arg))
261261
end
262262
expanded = try
263-
Base.invoke_in_world(macro_world, macfunc, macro_args...)
263+
Base.invoke_in_world(ctx.macro_world, macfunc, macro_args...)
264264
catch exc
265265
if exc isa MethodError && exc.f === macfunc
266-
if !isempty(methods_in_world(macfunc, Tuple{typeof(mctx), Vararg{Any}}, macro_world))
266+
if !isempty(methods_in_world(macfunc, Tuple{typeof(mctx), Vararg{Any}}, ctx.macro_world))
267267
# If the macro has at least some methods implemented in the
268268
# new style, assume the user meant to call one of those
269269
# rather than any old-style macro methods which might exist
270-
exc = MethodError(macfunc, (prepare_macro_args(ctx, mctx, raw_args)..., ), macro_world)
270+
exc = MethodError(macfunc, (prepare_macro_args(ctx, mctx, raw_args)..., ), ctx.macro_world)
271271
end
272272
end
273273
rethrow(MacroExpansionError(mctx, ex, "Error expanding macro", :all, exc))
@@ -280,7 +280,7 @@ function expand_macro(ctx, ex)
280280
# Module scope for the returned AST is the module where this particular
281281
# method was defined (may be different from `parentmodule(macfunc)`)
282282
mod_for_ast = lookup_method_instance(macfunc, macro_args,
283-
macro_world).def.module
283+
ctx.macro_world).def.module
284284
new_layer = ScopeLayer(length(ctx.scope_layers)+1, mod_for_ast,
285285
current_layer_id(ctx), true)
286286
push!(ctx.scope_layers, new_layer)
@@ -460,18 +460,18 @@ function expand_forms_1(ctx::MacroExpansionContext, ex::SyntaxTree)
460460
end
461461
end
462462

463-
function expand_forms_1(mod::Module, ex::SyntaxTree, expr_compat_mode::Bool)
463+
function expand_forms_1(mod::Module, ex::SyntaxTree, expr_compat_mode::Bool, macro_world::UInt)
464464
graph = ensure_attributes(syntax_graph(ex),
465465
var_id=IdTag,
466466
scope_layer=LayerId,
467467
__macro_ctx__=Nothing,
468468
meta=CompileHints)
469-
ctx = MacroExpansionContext(graph, mod, expr_compat_mode)
469+
ctx = MacroExpansionContext(graph, mod, expr_compat_mode, macro_world)
470470
ex2 = expand_forms_1(ctx, reparent(ctx, ex))
471471
graph2 = delete_attributes(graph, :__macro_ctx__)
472472
# TODO: Returning the context with pass-specific mutable data is a bad way
473473
# to carry state into the next pass. We might fix this by attaching such
474474
# data to the graph itself as global attributes?
475-
ctx2 = MacroExpansionContext(graph2, ctx.bindings, ctx.scope_layers, LayerId[], expr_compat_mode)
475+
ctx2 = MacroExpansionContext(graph2, ctx.bindings, ctx.scope_layers, LayerId[], expr_compat_mode, macro_world)
476476
return ctx2, reparent(ctx2, ex2)
477477
end

src/runtime.jl

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -338,12 +338,9 @@ function (g::GeneratedFunctionStub)(world::UInt, source::Method, @nospecialize a
338338
#
339339
# TODO: Reduce duplication where possible.
340340

341-
mod = parentmodule(g.gen)
342-
343341
# Attributes from parsing
344342
graph = ensure_attributes(SyntaxGraph(), kind=Kind, syntax_flags=UInt16, source=SourceAttrType,
345343
value=Any, name_val=String)
346-
347344
# Attributes for macro expansion
348345
graph = ensure_attributes(graph,
349346
var_id=IdTag,
@@ -355,8 +352,11 @@ function (g::GeneratedFunctionStub)(world::UInt, source::Method, @nospecialize a
355352
is_toplevel_thunk=Bool
356353
)
357354

358-
# Macro expansion
359-
ctx1 = MacroExpansionContext(graph, mod, false)
355+
# Macro expansion. Looking at Core.GeneratedFunctionStub, it seems that
356+
# macros emitted by the generator are currently expanded in the latest
357+
# world, so do that for compatibility.
358+
macro_world = typemax(UInt)
359+
ctx1 = MacroExpansionContext(graph, source.module, false, macro_world)
360360

361361
# Run code generator - this acts like a macro expander and like a macro
362362
# expander it gets a MacroContext.
@@ -375,7 +375,8 @@ function (g::GeneratedFunctionStub)(world::UInt, source::Method, @nospecialize a
375375
# Expand any macros emitted by the generator
376376
ex1 = expand_forms_1(ctx1, reparent(ctx1, ex0))
377377
ctx1 = MacroExpansionContext(delete_attributes(graph, :__macro_ctx__),
378-
ctx1.bindings, ctx1.scope_layers, LayerId[], false)
378+
ctx1.bindings, ctx1.scope_layers,
379+
LayerId[], false, macro_world)
379380
ex1 = reparent(ctx1, ex1)
380381

381382
# Desugaring
@@ -398,6 +399,21 @@ function (g::GeneratedFunctionStub)(world::UInt, source::Method, @nospecialize a
398399
ctx5, ex5 = linearize_ir(ctx4, ex4)
399400
ci = to_lowered_expr(mod, ex5)
400401
@assert ci isa Core.CodeInfo
402+
403+
# See GeneratedFunctionStub code in base/expr.jl
404+
ci.isva = source.isva
405+
code = ci.code
406+
bindings = IdSet{Core.Binding}()
407+
for i = 1:length(code)
408+
stmt = code[i]
409+
if isa(stmt, GlobalRef)
410+
push!(bindings, convert(Core.Binding, stmt))
411+
end
412+
end
413+
if !isempty(bindings)
414+
ci.edges = Core.svec(bindings...)
415+
end
416+
401417
return ci
402418
end
403419

src/syntax_graph.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,7 @@ macro SyntaxTree(ex_old)
689689
ex = _find_SyntaxTree_macro(full_ex, __source__.line)
690690
# 4. Do the first step of JuliaLowering's syntax lowering to get
691691
# syntax interpolations to work
692-
_, ex1 = expand_forms_1(__module__, ex, false)
692+
_, ex1 = expand_forms_1(__module__, ex, false, Base.tls_world_age())
693693
@assert kind(ex1) == K"call" && ex1[1].value == interpolate_ast
694694
Expr(:call, :interpolate_ast, SyntaxTree, ex1[3][1],
695695
map(e->_scope_layer_1_to_esc!(Expr(e)), ex1[4:end])...)

test/demo.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ function formatsrc(ex; kws...)
3333
end
3434

3535
function debug_lower(mod, ex; expr_compat_mode=false, verbose=false, do_eval=false)
36-
ctx1, ex_macroexpand = JuliaLowering.expand_forms_1(mod, ex, expr_compat_mode)
36+
ctx1, ex_macroexpand = JuliaLowering.expand_forms_1(mod, ex, expr_compat_mode, Base.get_world_counter())
3737

3838
verbose && @info "Macro expanded" formatsrc(ex_macroexpand, color_by=:scope_layer)
3939

test/functions.jl

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -444,19 +444,32 @@ end
444444
function f_partially_gen(x::NTuple{N,T}) where {N,T}
445445
shared = :shared_stuff
446446
if @generated
447+
if N == 2
448+
error("intentionally broken codegen (will trigger nongen branch)")
449+
end
447450
quote
448-
unshared = ($x, $N, $T)
451+
unshared = (:gen, ($x, $N, $T))
449452
end
450453
else
451-
# Uuuum. How do we actually test both sides of this branch???
452-
unshared = :nongen # (typeof(x), N, T)
454+
unshared = (:nongen, (typeof(x), N, T))
453455
end
454456
(shared, unshared)
455457
end
456458
457-
f_partially_gen((1,2,3,4,5))
459+
(f_partially_gen((1,2)), f_partially_gen((1,2,3,4,5)))
458460
end
459-
""") == (:shared_stuff, (NTuple{5,Int}, 5, Int))
461+
""") == ((:shared_stuff, (:nongen, (NTuple{2,Int}, 2, Int))),
462+
(:shared_stuff, (:gen, (NTuple{5,Int}, 5, Int))))
463+
464+
# Test generated function edges to bindings
465+
# (see also https://github.com/JuliaLang/julia/pull/57230)
466+
JuliaLowering.include_string(test_mod, raw"""
467+
const delete_me = 4
468+
@generated f_generated_return_delete_me() = return :(delete_me)
469+
""")
470+
@test test_mod.f_generated_return_delete_me() == 4
471+
Base.delete_binding(test_mod, :delete_me)
472+
@test_throws UndefVarError test_mod.f_generated_return_delete_me()
460473
end
461474

462475
@testset "Broadcast" begin

test/macros.jl

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
module macros
1+
@testset "macro tests" begin
22

3-
using JuliaLowering, Test
4-
5-
module test_mod end
3+
test_mod = Module(:macro_test)
64

75
JuliaLowering.include_string(test_mod, raw"""
86
module M
@@ -110,12 +108,33 @@ M.@recursive 3
110108
""") == (3, (2, (1, 0)))
111109

112110
ex = JuliaLowering.parsestmt(JuliaLowering.SyntaxTree, "M.@outer()", filename="foo.jl")
113-
ctx, expanded = JuliaLowering.expand_forms_1(test_mod, ex, false)
111+
ctx, expanded = JuliaLowering.expand_forms_1(test_mod, ex, false, Base.get_world_counter())
114112
@test JuliaLowering.sourcetext.(JuliaLowering.flattened_provenance(expanded[2])) == [
115113
"M.@outer()"
116114
"@inner"
117115
"y"
118116
]
117+
118+
# World age support for macro expansion
119+
JuliaLowering.include_string(test_mod, raw"""
120+
macro world_age_test()
121+
:(world1)
122+
end
123+
""")
124+
world1 = Base.get_world_counter()
125+
JuliaLowering.include_string(test_mod, raw"""
126+
macro world_age_test()
127+
:(world2)
128+
end
129+
""")
130+
world2 = Base.get_world_counter()
131+
132+
call_world_arg_test = JuliaLowering.parsestmt(JuliaLowering.SyntaxTree, "@world_age_test()")
133+
@test JuliaLowering.expand_forms_1(test_mod, call_world_arg_test, false, world1)[2]
134+
@ast_ "world1"::K"Identifier"
135+
@test JuliaLowering.expand_forms_1(test_mod, call_world_arg_test, false, world2)[2]
136+
@ast_ "world2"::K"Identifier"
137+
119138
# Layer parenting
120139
@test expanded[1].scope_layer == 2
121140
@test expanded[2].scope_layer == 3
@@ -233,7 +252,7 @@ end
233252
catch exc
234253
sprint(showerror, exc)
235254
end == """
236-
MacroExpansionError while expanding @oldstyle_error in module Main.macros.test_mod:
255+
MacroExpansionError while expanding @oldstyle_error in module Main.macro_test:
237256
@oldstyle_error
238257
└─────────────┘ ── Error expanding macro
239258
Caused by:
@@ -290,10 +309,10 @@ end
290309
err = try
291310
JuliaLowering.include_string(test_mod, "@sig_mismatch(1, 2, 3, 4)") === 1
292311
catch exc
293-
sprint(showerror, exc, context=:module=>@__MODULE__)
312+
sprint(showerror, exc, context=:module=>test_mod)
294313
end
295314
@test startswith(err, """
296-
MacroExpansionError while expanding @sig_mismatch in module Main.macros.test_mod:
315+
MacroExpansionError while expanding @sig_mismatch in module Main.macro_test:
297316
@sig_mismatch(1, 2, 3, 4)
298317
└───────────────────────┘ ── Error expanding macro
299318
Caused by:
@@ -329,4 +348,4 @@ end
329348
""") === (false, false, false, false)
330349
end
331350

332-
end # module macros
351+
end

0 commit comments

Comments
 (0)