Skip to content

Commit 7a72520

Browse files
authored
Merge pull request #84 from c42f/caf/toplevel-interpret-modules
Incremental lowering API
2 parents 134d4ad + 2b487e1 commit 7a72520

File tree

7 files changed

+216
-123
lines changed

7 files changed

+216
-123
lines changed

src/desugaring.jl

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function DesugaringContext(ctx, expr_compat_mode::Bool)
1919
DesugaringContext(graph,
2020
ctx.bindings,
2121
ctx.scope_layers,
22-
first(ctx.scope_layers).mod,
22+
current_layer(ctx).mod,
2323
expr_compat_mode)
2424
end
2525

@@ -4270,39 +4270,6 @@ function expand_public(ctx, ex)
42704270
]
42714271
end
42724272

4273-
#-------------------------------------------------------------------------------
4274-
# Expand module definitions
4275-
4276-
function expand_module(ctx, ex::SyntaxTree)
4277-
modname_ex = ex[1]
4278-
@chk kind(modname_ex) == K"Identifier"
4279-
modname = modname_ex.name_val
4280-
4281-
std_defs = !has_flags(ex, JuliaSyntax.BARE_MODULE_FLAG)
4282-
4283-
body = ex[2]
4284-
@chk kind(body) == K"block"
4285-
4286-
@ast ctx ex [K"block"
4287-
[K"assert"
4288-
"global_toplevel_only"::K"Symbol"
4289-
[K"inert" ex]
4290-
]
4291-
[K"call"
4292-
eval_module ::K"Value"
4293-
ctx.mod ::K"Value"
4294-
modname ::K"String"
4295-
std_defs ::K"Bool"
4296-
ctx.expr_compat_mode ::K"Bool"
4297-
[K"inert"(body)
4298-
[K"toplevel"
4299-
children(body)...
4300-
]
4301-
]
4302-
]
4303-
]
4304-
end
4305-
43064273
#-------------------------------------------------------------------------------
43074274
# Expand docstring-annotated expressions
43084275

@@ -4477,7 +4444,7 @@ function expand_forms_2(ctx::DesugaringContext, ex::SyntaxTree, docs=nothing)
44774444
elseif k == K"$"
44784445
throw(LoweringError(ex, "`\$` expression outside string or quote block"))
44794446
elseif k == K"module"
4480-
expand_module(ctx, ex)
4447+
throw(LoweringError(ex, "`module` is only allowed at top level"))
44814448
elseif k == K"import" || k == K"using"
44824449
expand_import_or_using(ctx, ex)
44834450
elseif k == K"export" || k == K"public"

src/eval.jl

Lines changed: 160 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Non-incremental lowering API for non-toplevel non-module expressions.
2+
# May be removed?
3+
14
function lower(mod::Module, ex0; expr_compat_mode=false, world=Base.get_world_counter())
25
ctx1, ex1 = expand_forms_1( mod, ex0, expr_compat_mode, world)
36
ctx2, ex2 = expand_forms_2( ctx1, ex1)
@@ -12,6 +15,96 @@ function macroexpand(mod::Module, ex; expr_compat_mode=false, world=Base.get_wor
1215
ex1
1316
end
1417

18+
# Incremental lowering API which can manage toplevel and module expressions.
19+
#
20+
# This iteration API is oddly bespoke and arguably somewhat non-Julian for two
21+
# reasons:
22+
#
23+
# * Lowering knows when new modules are required, and may request them with
24+
# `:begin_module`. However `eval()` generates those modules so they need to
25+
# be passed back into lowering. So we can't just use `Base.iterate()`. (Put a
26+
# different way, we have a situation which is suited to coroutines but we
27+
# don't want to use full Julia `Task`s for this.)
28+
# * We might want to implement this `eval()` in Julia's C runtime code or early
29+
# in bootstrap. Hence using SimpleVector and Symbol as the return values of
30+
# `lower_step()`
31+
#
32+
# We might consider changing at least the second of these choices, depending on
33+
# how we end up putting this into Base.
34+
35+
struct LoweringIterator{GraphType}
36+
ctx::MacroExpansionContext{GraphType}
37+
todo::Vector{Tuple{SyntaxTree{GraphType}, Bool, Int}}
38+
end
39+
40+
function lower_init(ex::SyntaxTree, mod::Module, macro_world::UInt; expr_compat_mode::Bool=false)
41+
graph = ensure_macro_attributes(syntax_graph(ex))
42+
ctx = MacroExpansionContext(graph, mod, expr_compat_mode, macro_world)
43+
ex = reparent(ctx, ex)
44+
LoweringIterator{typeof(graph)}(ctx, [(ex, false, 0)])
45+
end
46+
47+
function lower_step(iter, push_mod=nothing)
48+
if !isnothing(push_mod)
49+
push_layer!(iter.ctx, push_mod, false)
50+
end
51+
52+
if isempty(iter.todo)
53+
return Core.svec(:done)
54+
end
55+
56+
ex, is_module_body, child_idx = pop!(iter.todo)
57+
if child_idx > 0
58+
next_child = child_idx + 1
59+
if child_idx <= numchildren(ex)
60+
push!(iter.todo, (ex, is_module_body, next_child))
61+
ex = ex[child_idx]
62+
else
63+
if is_module_body
64+
pop_layer!(iter.ctx)
65+
return Core.svec(:end_module)
66+
else
67+
return lower_step(iter)
68+
end
69+
end
70+
end
71+
72+
k = kind(ex)
73+
if !(k in KSet"toplevel module")
74+
ex = expand_forms_1(iter.ctx, ex)
75+
k = kind(ex)
76+
end
77+
if k == K"toplevel"
78+
push!(iter.todo, (ex, false, 1))
79+
return lower_step(iter)
80+
elseif k == K"module"
81+
name = ex[1]
82+
if kind(name) != K"Identifier"
83+
throw(LoweringError(name, "Expected module name"))
84+
end
85+
newmod_name = Symbol(name.name_val)
86+
body = ex[2]
87+
if kind(body) != K"block"
88+
throw(LoweringError(body, "Expected block in module body"))
89+
end
90+
std_defs = !has_flags(ex, JuliaSyntax.BARE_MODULE_FLAG)
91+
loc = source_location(LineNumberNode, ex)
92+
push!(iter.todo, (body, true, 1))
93+
return Core.svec(:begin_module, newmod_name, std_defs, loc)
94+
else
95+
# Non macro expansion parts of lowering
96+
ctx2, ex2 = expand_forms_2(iter.ctx, ex)
97+
ctx3, ex3 = resolve_scopes(ctx2, ex2)
98+
ctx4, ex4 = convert_closures(ctx3, ex3)
99+
ctx5, ex5 = linearize_ir(ctx4, ex4)
100+
thunk = to_lowered_expr(ex5)
101+
return Core.svec(:thunk, thunk)
102+
end
103+
end
104+
105+
106+
#-------------------------------------------------------------------------------
107+
15108
function codeinfo_has_image_globalref(@nospecialize(e))
16109
if e isa GlobalRef
17110
return 0x00 !== @ccall jl_object_in_image(e.mod::Any)::UInt8
@@ -274,7 +367,7 @@ function _to_lowered_expr(ex::SyntaxTree, stmt_offset::Int)
274367
elseif k == K"code_info"
275368
ir = to_code_info(ex[1], ex.slots)
276369
if ex.is_toplevel_thunk
277-
Expr(:thunk, ir)
370+
Expr(:thunk, ir) # TODO: Maybe nice to just return a CodeInfo here?
278371
else
279372
ir
280373
end
@@ -357,19 +450,74 @@ function _to_lowered_expr(ex::SyntaxTree, stmt_offset::Int)
357450
end
358451

359452
#-------------------------------------------------------------------------------
360-
# Our version of eval takes our own data structures
361-
@fzone "JL: eval" function eval(mod::Module, ex::SyntaxTree; expr_compat_mode::Bool=false)
362-
k = kind(ex)
363-
if k == K"toplevel"
364-
x = nothing
365-
for e in children(ex)
366-
x = eval(mod, e; expr_compat_mode)
453+
# Our version of eval - should be upstreamed though?
454+
@fzone "JL: eval" function eval(mod::Module, ex::SyntaxTree;
455+
macro_world::UInt=Base.get_world_counter(),
456+
opts...)
457+
iter = lower_init(ex, mod, macro_world; opts...)
458+
_eval(mod, iter)
459+
end
460+
461+
if VERSION >= v"1.13.0-DEV.1199" # https://github.com/JuliaLang/julia/pull/59604
462+
463+
function _eval(mod, iter)
464+
modules = Module[]
465+
new_mod = nothing
466+
result = nothing
467+
while true
468+
thunk = lower_step(iter, new_mod)::Core.SimpleVector
469+
new_mod = nothing
470+
type = thunk[1]::Symbol
471+
if type == :done
472+
break
473+
elseif type == :begin_module
474+
push!(modules, mod)
475+
mod = @ccall jl_begin_new_module(mod::Any, thunk[2]::Symbol, thunk[3]::Cint,
476+
thunk[4].file::Cstring, thunk[4].line::Cint)::Module
477+
new_mod = mod
478+
elseif type == :end_module
479+
@ccall jl_end_new_module(mod::Module)::Cvoid
480+
result = mod
481+
mod = pop!(modules)
482+
else
483+
@assert type == :thunk
484+
result = Core.eval(mod, thunk[2])
485+
end
486+
end
487+
@assert isempty(modules)
488+
return result
489+
end
490+
491+
else
492+
493+
function _eval(mod, iter, new_mod=nothing)
494+
in_new_mod = !isnothing(new_mod)
495+
result = nothing
496+
while true
497+
thunk = lower_step(iter, new_mod)::Core.SimpleVector
498+
new_mod = nothing
499+
type = thunk[1]::Symbol
500+
if type == :done
501+
@assert !in_new_mod
502+
break
503+
elseif type == :begin_module
504+
name = thunk[2]::Symbol
505+
std_defs = thunk[3]
506+
result = Core.eval(mod,
507+
Expr(:module, std_defs, name,
508+
Expr(:block, thunk[4], Expr(:call, m->_eval(m, iter, m), name)))
509+
)
510+
elseif type == :end_module
511+
@assert in_new_mod
512+
return mod
513+
else
514+
@assert type == :thunk
515+
result = Core.eval(mod, thunk[2])
367516
end
368-
return x
369517
end
370-
linear_ir = lower(mod, ex; expr_compat_mode)
371-
thunk = to_lowered_expr(linear_ir)
372-
Core.eval(mod, thunk)
518+
return result
519+
end
520+
373521
end
374522

375523
"""

src/macro_expansion.jl

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ function MacroExpansionContext(graph::SyntaxGraph, mod::Module, expr_compat_mode
2929
MacroExpansionContext(graph, Bindings(), layers, LayerId[length(layers)], expr_compat_mode, world)
3030
end
3131

32+
function push_layer!(ctx::MacroExpansionContext, mod::Module, is_macro_expansion::Bool)
33+
new_layer = ScopeLayer(length(ctx.scope_layers)+1, mod,
34+
current_layer_id(ctx), is_macro_expansion)
35+
push!(ctx.scope_layers, new_layer)
36+
push!(ctx.scope_layer_stack, new_layer.id)
37+
end
38+
function pop_layer!(ctx::MacroExpansionContext)
39+
pop!(ctx.scope_layer_stack)
40+
end
41+
3242
current_layer(ctx::MacroExpansionContext) = ctx.scope_layers[last(ctx.scope_layer_stack)]
3343
current_layer_id(ctx::MacroExpansionContext) = last(ctx.scope_layer_stack)
3444

@@ -342,10 +352,9 @@ function expand_macro(ctx, ex)
342352
expanded = fix_toplevel_expansion(ctx, expanded, mod_for_ast, macro_loc)
343353
new_layer = ScopeLayer(length(ctx.scope_layers)+1, mod_for_ast,
344354
current_layer_id(ctx), true)
345-
push!(ctx.scope_layers, new_layer)
346-
push!(ctx.scope_layer_stack, new_layer.id)
355+
push_layer!(ctx, mod_for_ast, true)
347356
expanded = expand_forms_1(ctx, expanded)
348-
pop!(ctx.scope_layer_stack)
357+
pop_layer!(ctx)
349358
end
350359
return expanded
351360
end
@@ -520,23 +529,28 @@ function expand_forms_1(ctx::MacroExpansionContext, ex::SyntaxTree)
520529
end
521530
end
522531

532+
function ensure_macro_attributes(graph)
533+
ensure_attributes(graph,
534+
var_id=IdTag,
535+
scope_layer=LayerId,
536+
__macro_ctx__=Nothing,
537+
meta=CompileHints)
538+
end
539+
523540
@fzone "JL: macroexpand" function expand_forms_1(mod::Module, ex::SyntaxTree, expr_compat_mode::Bool, macro_world::UInt)
524541
if kind(ex) == K"local"
525542
# This error assumes we're expanding the body of a top level thunk but
526543
# we might want to make that more explicit in the pass system.
527544
throw(LoweringError(ex, "local declarations have no effect outside a scope"))
528545
end
529-
graph = ensure_attributes(syntax_graph(ex),
530-
var_id=IdTag,
531-
scope_layer=LayerId,
532-
__macro_ctx__=Nothing,
533-
meta=CompileHints)
546+
graph = ensure_macro_attributes(syntax_graph(ex))
534547
ctx = MacroExpansionContext(graph, mod, expr_compat_mode, macro_world)
535548
ex2 = expand_forms_1(ctx, reparent(ctx, ex))
536549
graph2 = delete_attributes(graph, :__macro_ctx__)
537550
# TODO: Returning the context with pass-specific mutable data is a bad way
538551
# to carry state into the next pass. We might fix this by attaching such
539552
# data to the graph itself as global attributes?
540-
ctx2 = MacroExpansionContext(graph2, ctx.bindings, ctx.scope_layers, LayerId[], expr_compat_mode, macro_world)
553+
ctx2 = MacroExpansionContext(graph2, ctx.bindings, ctx.scope_layers, ctx.scope_layer_stack,
554+
expr_compat_mode, macro_world)
541555
return ctx2, reparent(ctx2, ex2)
542556
end

src/runtime.jl

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -207,35 +207,6 @@ end
207207
#--------------------------------------------------
208208
# Functions which create modules or mutate their bindings
209209

210-
# Construct new bare module including only the "default names"
211-
#
212-
# using Core
213-
# const modname = modval
214-
# public modname
215-
#
216-
# And run statments in the toplevel expression `body`
217-
function eval_module(parentmod::Module, modname::AbstractString, std_defs::Bool,
218-
expr_compat_mode::Bool, body::SyntaxTree)
219-
# Here we just use `eval()` with an Expr.
220-
# If we wanted to avoid this we'd need to reproduce a lot of machinery from
221-
# jl_eval_module_expr()
222-
#
223-
# 1. Register / deparent toplevel modules
224-
# 2. Set binding in parent module
225-
# 3. Deal with replacing modules
226-
# * Warn if replacing
227-
# * Root old module being replaced
228-
# 4. Run __init__
229-
# * Also run __init__ for any children after parent is defined
230-
# mod = @ccall jl_new_module(Symbol(modname)::Symbol, parentmod::Module)::Any
231-
# ...
232-
name = Symbol(modname)
233-
eval_module_body(mod) = eval(mod, body; expr_compat_mode=expr_compat_mode)
234-
Core.eval(parentmod,
235-
Expr(:module, std_defs, name,
236-
Expr(:block, Expr(:call, eval_module_body, name))))
237-
end
238-
239210
const _Base_has_eval_import = isdefined(Base, :_eval_import)
240211

241212
function eval_import(imported::Bool, to::Module, from::Union{Expr, Nothing}, paths::Expr...)
@@ -357,11 +328,7 @@ function (g::GeneratedFunctionStub)(world::UInt, source::Method, @nospecialize a
357328
graph = ensure_attributes(SyntaxGraph(), kind=Kind, syntax_flags=UInt16, source=SourceAttrType,
358329
value=Any, name_val=String)
359330
# Attributes for macro expansion
360-
graph = ensure_attributes(graph,
361-
var_id=IdTag,
362-
scope_layer=LayerId,
363-
__macro_ctx__=Nothing,
364-
meta=CompileHints,
331+
graph = ensure_attributes(ensure_macro_attributes(graph),
365332
# Additional attribute for resolve_scopes, for
366333
# adding our custom lambda below
367334
is_toplevel_thunk=Bool
@@ -393,7 +360,7 @@ function (g::GeneratedFunctionStub)(world::UInt, source::Method, @nospecialize a
393360
ex1 = expand_forms_1(ctx1, reparent(ctx1, ex0))
394361
ctx1 = MacroExpansionContext(delete_attributes(graph, :__macro_ctx__),
395362
ctx1.bindings, ctx1.scope_layers,
396-
LayerId[], false, macro_world)
363+
ctx1.scope_layer_stack, false, macro_world)
397364
ex1 = reparent(ctx1, ex1)
398365

399366
# Desugaring

test/demo.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ end
7474

7575
#-------------------------------------------------------------------------------
7676
# Module containing macros used in the demo.
77-
define_macros = true
77+
define_macros = false
7878
if !define_macros
7979
eval(:(module M end))
8080
else
@@ -893,7 +893,7 @@ ex = parsestmt(SyntaxTree, src, filename="foo.jl")
893893
ctx3, ex_scoped,
894894
ctx4, ex_converted,
895895
ctx5, ex_compiled,
896-
ex_expr, eval_result) = debug_lower(M, ex; verbose=true)
896+
ex_expr, eval_result) = debug_lower(M, ex; verbose=true, do_eval=true)
897897

898898
# Automatic test reduction
899899
# bad = reduce_any_failing_toplevel(JuliaLowering, joinpath(@__DIR__, "../src/desugaring.jl"))

0 commit comments

Comments
 (0)