Skip to content

Commit 3b73976

Browse files
committed
Incremental lowering API
Julia's incrementally evaluated top level semantics make it rather tricky to design a lowering interface for top level and module level expressions. Currently these expressions are effectively *interpreted* by eval rather than ever being processed by lowering. However, I'd like a cleaner separation between "low level evaluation" and lowering, such that Core can contain only the low level eval "driver function". I'd like to propose the split as follows: * "Low level" evaluation is about executing a sequence of thunks represented as `CodeInfo`, and potentially about creating modules for those to be executed inside. * Lowering is about expression processing. In principle, the runtime's view of `eval()` shouldn't know about `Expr` or `SyntaxTree` (or whatever AST we use) - that should be left to the compiler frontend. A useful way to think about the duties of the frontend is to consider the question "What if we wanted to host another language on top of the Julia runtime?". If we can eventually achieve that without ever generating Julia `Expr` then we will have succeeded in separating the frontend. To implement all this I've recast lowering as an incremental iterative API in this change. Thus it's the job of `eval()` to simply evaluate thunks and create new modules as driven by lowering. (Perhaps we'd move this definition of `eval()` over to the Julia runtime before 1.13.) Depends on JuliaLang/julia#59604
1 parent 7185a8a commit 3b73976

File tree

3 files changed

+119
-67
lines changed

3 files changed

+119
-67
lines changed

src/eval.jl

Lines changed: 106 additions & 63 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,81 @@ 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+
struct LoweringIterator{GraphType}
21+
ctx::MacroExpansionContext{GraphType}
22+
todo::Vector{Tuple{SyntaxTree{GraphType}, Bool, Int}}
23+
end
24+
25+
function lower_init(ex::SyntaxTree, mod::Module, macro_world::UInt; expr_compat_mode::Bool=false)
26+
graph = ensure_macro_attributes(syntax_graph(ex))
27+
ctx = MacroExpansionContext(graph, mod, expr_compat_mode, macro_world)
28+
ex = reparent(ctx, ex)
29+
LoweringIterator{typeof(graph)}(ctx, [(ex, false, 0)])
30+
end
31+
32+
function lower_step(iter, push_mod=nothing)
33+
if !isnothing(push_mod)
34+
push_layer!(iter.ctx, push_mod, false)
35+
end
36+
37+
if isempty(iter.todo)
38+
return Core.svec(:done)
39+
end
40+
41+
ex, is_module_body, child_idx = pop!(iter.todo)
42+
if child_idx > 0
43+
next_child = child_idx + 1
44+
if child_idx <= numchildren(ex)
45+
push!(iter.todo, (ex, is_module_body, next_child))
46+
ex = ex[child_idx]
47+
else
48+
if is_module_body
49+
pop_layer!(iter.ctx)
50+
return Core.svec(:end_module)
51+
else
52+
return lower_step(iter)
53+
end
54+
end
55+
end
56+
57+
k = kind(ex)
58+
if !(k in KSet"toplevel module")
59+
ex = expand_forms_1(iter.ctx, ex)
60+
k = kind(ex)
61+
end
62+
if k == K"toplevel"
63+
push!(iter.todo, (ex, false, 1))
64+
return lower_step(iter)
65+
elseif k == K"module"
66+
name = ex[1]
67+
if kind(name) != K"Identifier"
68+
throw(LoweringError(name, "Expected module name"))
69+
end
70+
newmod_name = Symbol(name.name_val)
71+
body = ex[2]
72+
if kind(body) != K"block"
73+
throw(LoweringError(body, "Expected block in module body"))
74+
end
75+
std_defs = !has_flags(ex, JuliaSyntax.BARE_MODULE_FLAG)
76+
loc = source_location(LineNumberNode, ex)
77+
push!(iter.todo, (body, true, 1))
78+
return Core.svec(:begin_module, newmod_name, std_defs, loc)
79+
else
80+
# Non macro expansion parts of lowering
81+
ctx2, ex2 = expand_forms_2(iter.ctx, ex)
82+
ctx3, ex3 = resolve_scopes(ctx2, ex2)
83+
ctx4, ex4 = convert_closures(ctx3, ex3)
84+
ctx5, ex5 = linearize_ir(ctx4, ex4)
85+
thunk = to_lowered_expr(ex5)
86+
return Core.svec(:thunk, thunk)
87+
end
88+
end
89+
90+
91+
#-------------------------------------------------------------------------------
92+
1593
function codeinfo_has_image_globalref(@nospecialize(e))
1694
if e isa GlobalRef
1795
return 0x00 !== @ccall jl_object_in_image(e.mod::Any)::UInt8
@@ -274,7 +352,7 @@ function _to_lowered_expr(ex::SyntaxTree, stmt_offset::Int)
274352
elseif k == K"code_info"
275353
ir = to_code_info(ex[1], ex.slots)
276354
if ex.is_toplevel_thunk
277-
Expr(:thunk, ir)
355+
Expr(:thunk, ir) # TODO: Maybe nice to just return a CodeInfo here?
278356
else
279357
ir
280358
end
@@ -357,74 +435,39 @@ function _to_lowered_expr(ex::SyntaxTree, stmt_offset::Int)
357435
end
358436

359437
#-------------------------------------------------------------------------------
360-
# Our version of eval takes our own data structures
438+
# Our version of eval - should be upstreamed though?
361439
@fzone "JL: eval" function eval(mod::Module, ex::SyntaxTree;
362-
expr_compat_mode::Bool=false, macro_world=Base.get_world_counter())
363-
graph = ensure_macro_attributes(syntax_graph(ex))
364-
ctx = MacroExpansionContext(graph, mod, expr_compat_mode, macro_world)
365-
_eval(ctx, ex)
366-
end
367-
368-
function _eval_stmts(ctx, exs)
369-
res = nothing
370-
for ex in exs
371-
res = _eval(ctx, ex)
372-
end
373-
res
374-
end
375-
376-
function _eval_module_body(ctx, mod, ex)
377-
new_layer = ScopeLayer(length(ctx.scope_layers)+1, mod,
378-
current_layer_id(ctx), false)
379-
push!(ctx.scope_layers, new_layer)
380-
push!(ctx.scope_layer_stack, new_layer.id)
381-
stmts = kind(ex[2]) == K"block" ? ex[2][1:end] : ex[2:2]
382-
_eval_stmts(ctx, stmts)
383-
pop!(ctx.scope_layer_stack)
384-
end
385-
386-
function _eval_module(ctx, ex)
387-
std_defs = !has_flags(ex, JuliaSyntax.BARE_MODULE_FLAG)
388-
newmod_name = Symbol(ex[1].name_val)
389-
parent = current_layer(ctx).mod
390-
391-
# Core.eval(parent,
392-
# Expr(:module, std_defs, newmod_name,
393-
# Expr(:block, Expr(:call,
394-
# newmod->_eval_module_body(ctx, newmod, ex),
395-
# newmod_name))))
396-
397-
# https://github.com/JuliaLang/julia/pull/59604
398-
loc = source_location(LineNumberNode, ex)
399-
newmod = @ccall jl_begin_new_module(parent::Any, newmod_name::Symbol, std_defs::Cint,
400-
loc.file::Cstring, loc.line::Cint)::Module
401-
_eval_module_body(ctx, newmod, ex)
402-
@ccall jl_end_new_module(newmod::Module)::Cvoid
403-
404-
return newmod
405-
end
406-
407-
function _eval(ctx, ex::SyntaxTree)
408-
k = kind(ex)
409-
if k == K"toplevel"
410-
_eval_stmts(ctx, children(ex))
411-
elseif k == K"module"
412-
_eval_module(ctx, ex)
413-
else
414-
ex1 = expand_forms_1(ctx, ex)
415-
if kind(ex1) in KSet"toplevel module"
416-
_eval(ctx, ex1)
440+
macro_world::UInt=Base.get_world_counter(),
441+
opts...)
442+
modules = Module[]
443+
iter = lower_init(ex, mod, macro_world; opts...)
444+
new_mod = nothing
445+
result = nothing
446+
while true
447+
thunk = lower_step(iter, new_mod)::Core.SimpleVector
448+
new_mod = nothing
449+
type = thunk[1]::Symbol
450+
if type == :done
451+
break
452+
elseif type == :begin_module
453+
push!(modules, mod)
454+
mod = @ccall jl_begin_new_module(mod::Any, thunk[2]::Symbol, thunk[3]::Cint,
455+
thunk[4].file::Cstring, thunk[4].line::Cint)::Module
456+
new_mod = mod
457+
elseif type == :end_module
458+
@ccall jl_end_new_module(mod::Module)::Cvoid
459+
result = mod
460+
mod = pop!(modules)
417461
else
418-
ctx2, ex2 = expand_forms_2(ctx, ex1)
419-
ctx3, ex3 = resolve_scopes(ctx2, ex2)
420-
ctx4, ex4 = convert_closures(ctx3, ex3)
421-
ctx5, ex5 = linearize_ir(ctx4, ex4)
422-
thunk = to_lowered_expr(ex5)
423-
Core.eval(current_layer(ctx).mod, thunk)
462+
@assert type == :thunk
463+
result = Core.eval(mod, thunk[2])
424464
end
425465
end
466+
@assert isempty(modules)
467+
return result
426468
end
427469

470+
428471
"""
429472
include(mod::Module, path::AbstractString)
430473

src/macro_expansion.jl

Lines changed: 12 additions & 3 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

test/demo.jl

Lines changed: 1 addition & 1 deletion
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

0 commit comments

Comments
 (0)