Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ jobs:
fail-fast: false
matrix:
version:
- '~1.12.0-rc1'
- 'nightly'
os:
- ubuntu-latest
Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -898,8 +898,6 @@ In the current Julia runtime,
* `:import`
* `:public`
* `:export`
* `:global`
* `:const`
* `:toplevel`
* `:error`
* `:incomplete`
Expand Down
70 changes: 57 additions & 13 deletions src/closure_conversion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ struct ClosureConversionCtx{GraphType} <: AbstractLoweringContext
# True if we're in a section of code which preserves top-level sequencing
# such that closure types can be emitted inline with other code.
is_toplevel_seq_point::Bool
# True if this expression should not have toplevel effects, namely, it
# should not declare the globals it references. This allows generated
# functions to refer to globals that have already been declared, without
# triggering the "function body AST not pure" error.
toplevel_pure::Bool
toplevel_stmts::SyntaxList{GraphType}
closure_infos::Dict{IdTag,ClosureInfo{GraphType}}
end
Expand All @@ -27,7 +32,8 @@ function ClosureConversionCtx(graph::GraphType, bindings::Bindings,
lambda_bindings::LambdaBindings) where {GraphType}
ClosureConversionCtx{GraphType}(
graph, bindings, mod, closure_bindings, nothing,
lambda_bindings, false, SyntaxList(graph), Dict{IdTag,ClosureInfo{GraphType}}())
lambda_bindings, false, true, SyntaxList(graph),
Dict{IdTag,ClosureInfo{GraphType}}())
end

function current_lambda_bindings(ctx::ClosureConversionCtx)
Expand Down Expand Up @@ -117,10 +123,39 @@ function convert_for_type_decl(ctx, srcref, ex, type, do_typeassert)
]
end

# TODO: Avoid producing redundant calls to declare_global
function make_globaldecl(ctx, src_ex, mod, name, strong=false, type=nothing; ret_nothing=false)
if !ctx.toplevel_pure
decl = @ast ctx src_ex [K"block"
[K"call"
"declare_global"::K"core"
mod::K"Value" name::K"Symbol" strong::K"Bool"
if type !== nothing
type
end
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if isn't required - children of type Nothing will be elided from @ast child list - we're allowed to do this without ambiguity because AST fragments must always be of type SyntaxTree.

]
[K"latestworld"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most world age increments have been moved to linearization in JuliaLowering; move this if you can

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realizing I mixed up which file I was commenting on, sorry. I was worried about emitting (globaldecl ...) (latestworld) in desugaring and sending that through the woodchipper (closure conversion)

@ast ctx src_ex [K"removable" "nothing"::K"core"]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for the nested @ast here :)

]
if ctx.is_toplevel_seq_point
return decl
else
push!(ctx.toplevel_stmts, decl)
end
end
if ret_nothing
nothing
else
@ast ctx src_ex [K"removable" "nothing"::K"core"]
Copy link
Owner

@c42f c42f Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"nothing"::K"core" is always removable as is access to other core bindings which we know cannot have side effects - so it's not necessary to wrap this.

end
end

function convert_global_assignment(ctx, ex, var, rhs0)
binfo = lookup_binding(ctx, var)
@assert binfo.kind == :global
stmts = SyntaxList(ctx)
decl = make_globaldecl(ctx, ex, binfo.mod, binfo.name, true; ret_nothing=true)
decl !== nothing && push!(stmts, decl)
rhs1 = if is_simple_atom(ctx, rhs0)
rhs0
else
Expand All @@ -147,7 +182,6 @@ function convert_global_assignment(ctx, ex, var, rhs0)
end
push!(stmts, @ast ctx ex [K"=" var rhs])
@ast ctx ex [K"block"
[K"globaldecl" var]
stmts...
rhs1
]
Expand Down Expand Up @@ -296,7 +330,7 @@ function map_cl_convert(ctx::ClosureConversionCtx, ex, toplevel_preserving)
toplevel_stmts = SyntaxList(ctx)
ctx2 = ClosureConversionCtx(ctx.graph, ctx.bindings, ctx.mod,
ctx.closure_bindings, ctx.capture_rewriting, ctx.lambda_bindings,
false, toplevel_stmts, ctx.closure_infos)
false, ctx.toplevel_pure, toplevel_stmts, ctx.closure_infos)
res = mapchildren(e->_convert_closures(ctx2, e), ctx2, ex)
if isempty(toplevel_stmts)
res
Expand Down Expand Up @@ -352,16 +386,24 @@ function _convert_closures(ctx::ClosureConversionCtx, ex)
@assert kind(ex[1]) == K"BindingId"
binfo = lookup_binding(ctx, ex[1])
if binfo.kind == :global
@ast ctx ex [K"block"
# flisp has this, but our K"assert" handling is in a previous pass
# [K"assert" "toplevel_only"::K"Symbol" [K"inert" ex]]
[K"globaldecl"
ex[1]
_convert_closures(ctx, ex[2])]
"nothing"::K"core"]
# flisp has this, but our K"assert" handling is in a previous pass
# [K"assert" "toplevel_only"::K"Symbol" [K"inert" ex]]
make_globaldecl(ctx, ex, binfo.mod, binfo.name, true, _convert_closures(ctx, ex[2]))
else
makeleaf(ctx, ex, K"TOMBSTONE")
end
elseif k == K"global"
# Leftover `global` forms become weak globals.
mod, name = if kind(ex[1]) == K"BindingId"
binfo = lookup_binding(ctx, ex[1])
@assert binfo.kind == :global
binfo.mod, binfo.name
else
# See note about using eval on Expr(:global/:const, GlobalRef(...))
@assert ex[1].value isa GlobalRef
ex[1].value.mod, String(ex[1].value.name)
end
@ast ctx ex [K"unused_only" make_globaldecl(ctx, ex, mod, name, false)]
elseif k == K"local"
var = ex[1]
binfo = lookup_binding(ctx, var)
Expand Down Expand Up @@ -453,7 +495,8 @@ function _convert_closures(ctx::ClosureConversionCtx, ex)
cap_rewrite = is_closure ? ctx.closure_infos[name.var_id] : nothing
ctx2 = ClosureConversionCtx(ctx.graph, ctx.bindings, ctx.mod,
ctx.closure_bindings, cap_rewrite, ctx.lambda_bindings,
ctx.is_toplevel_seq_point, ctx.toplevel_stmts, ctx.closure_infos)
ctx.is_toplevel_seq_point, ctx.toplevel_pure, ctx.toplevel_stmts,
ctx.closure_infos)
body = map_cl_convert(ctx2, ex[2], false)
if is_closure
if ctx.is_toplevel_seq_point
Expand All @@ -478,7 +521,7 @@ function _convert_closures(ctx::ClosureConversionCtx, ex)

ctx2 = ClosureConversionCtx(ctx.graph, ctx.bindings, ctx.mod,
ctx.closure_bindings, capture_rewrites, ctx.lambda_bindings,
false, ctx.toplevel_stmts, ctx.closure_infos)
false, ctx.toplevel_pure, ctx.toplevel_stmts, ctx.closure_infos)

init_closure_args = SyntaxList(ctx)
for id in field_orig_bindings
Expand Down Expand Up @@ -521,7 +564,8 @@ function closure_convert_lambda(ctx, ex)
end
ctx2 = ClosureConversionCtx(ctx.graph, ctx.bindings, ctx.mod,
ctx.closure_bindings, cap_rewrite, lambda_bindings,
ex.is_toplevel_thunk, ctx.toplevel_stmts, ctx.closure_infos)
ex.is_toplevel_thunk, ctx.toplevel_pure && ex.toplevel_pure,
ctx.toplevel_stmts, ctx.closure_infos)
lambda_children = SyntaxList(ctx)
args = ex[1]
push!(lambda_children, args)
Expand Down
3 changes: 2 additions & 1 deletion src/compat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ function expr_to_syntaxtree(@nospecialize(e), lnn::Union{LineNumberNode, Nothing
kind=Kind, syntax_flags=UInt16,
source=SourceAttrType, var_id=Int, value=Any,
name_val=String, is_toplevel_thunk=Bool,
scope_layer=LayerId, meta=CompileHints)
scope_layer=LayerId, meta=CompileHints,
toplevel_pure=Bool)
expr_to_syntaxtree(graph, e, lnn)
end

Expand Down
Loading
Loading