Skip to content

Commit

Permalink
More systemantic treatment of bindings
Browse files Browse the repository at this point in the history
Introduce `Bindings` and `BindingInfo` structs, as better system and
naming to replace the `var_info` dict and `next_var_id` ref.

Introduce a new kind `K"BindingId"` - an integer handle for an entity
(variable, constant, etc). Normally these are generated from `NameKey`
(`name,scope_layer`) pairs written in the user's program. But they can
also be generated programmatically by the `ssavar()` function, or by
`new_mutable_var()`. This should allow proper "gensym" bindings from
macros, without needing to rely on the generation of unique names via
name mangling.

In this system, `SSAValue` is just a particular kind of binding,
relegated to a low level IR concept which has invariants which can, in
principle, be checked or inferred by the variable analysis pass.
  • Loading branch information
c42f committed Jul 3, 2024
1 parent dafaad7 commit 92c2197
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 150 deletions.
73 changes: 62 additions & 11 deletions src/ast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,61 @@
abstract type AbstractLoweringContext end

"""
Unique symbolic identity for a variable
Unique symbolic identity for a variable, constant, label, or other entity
"""
const VarId = Int
const IdTag = Int

"""
Metadata about a binding
"""
struct BindingInfo
name::String
mod::Union{Nothing,Module} # Set when `kind === :global`
kind::Symbol # :local :global :argument :static_parameter
is_ssa::Bool # Single assignment, defined before use
is_ambiguous_local::Bool # Local, but would be global in soft scope (ie, the REPL)
end

"""
Metadata about "entities" (variables, constants, etc) in the program. Each
entity is associated to a unique integer id, the BindingId. A binding will be
inferred for each *name* in the user's source program by symbolic analysis of
the source.
However, bindings can also be introduced programmatically during lowering or
macro expansion: the primary key for bindings is the `BindingId` integer, not
a name.
"""
struct Bindings
info::Vector{BindingInfo}
end

Bindings() = Bindings(Vector{BindingInfo}())

function new_binding(bindings::Bindings, info::BindingInfo)
push!(bindings.info, info)
return length(bindings.info)
end

function lookup_binding(bindings::Bindings, id::Integer)
bindings.info[id]
end

function lookup_binding(bindings::Bindings, ex::SyntaxTree)
# TODO: @assert kind(ex) == K"BindingId"
bindings.info[ex.var_id]
end

function lookup_binding(ctx::AbstractLoweringContext, id)
lookup_binding(ctx.bindings, id)
end

const LayerId = Int

function syntax_graph(ctx::AbstractLoweringContext)
ctx.graph
end

function new_var_id(ctx::AbstractLoweringContext)
id = ctx.next_var_id[]
ctx.next_var_id[] += 1
return id
end

#-------------------------------------------------------------------------------
# AST creation utilities
_node_id(ex::NodeId) = ex
Expand Down Expand Up @@ -67,7 +106,7 @@ function makeleaf(ctx, srcref, k::Kind, value; kws...)
graph = syntax_graph(ctx)
if k == K"Identifier" || k == K"core" || k == K"top" || k == K"Symbol" || k == K"globalref"
makeleaf(graph, srcref, k; name_val=value, kws...)
elseif k == K"SSAValue"
elseif k == K"BindingId"
makeleaf(graph, srcref, k; var_id=value, kws...)
elseif k == K"label"
makeleaf(graph, srcref, k; id=value, kws...)
Expand Down Expand Up @@ -104,9 +143,21 @@ unused(ctx, ex) = core_ref(ctx, ex, "UNUSED")

top_ref(ctx, ex, name) = makeleaf(ctx, ex, K"top", name)

# Create a new SSA variable
# Create a new SSA binding
function ssavar(ctx::AbstractLoweringContext, srcref)
makeleaf(ctx, srcref, K"SSAValue", var_id=new_var_id(ctx))
# TODO: Store this name in only one place? Probably use the provenance chain?
name = "ssa"
id = new_binding(ctx.bindings, BindingInfo(name, nothing, :local, true, false))
# Create an identifier
nameref = makeleaf(ctx, srcref, K"Identifier", name_val=name)
makeleaf(ctx, nameref, K"BindingId", var_id=id)
end

# Create a new local mutable variable
function new_mutable_var(ctx::AbstractLoweringContext, srcref, name)
id = new_binding(ctx, BindingInfo(name, nothing, :local, false, false))
nameref = makeleaf(ctx, srcref, K"Identifier", name_val=name)
makeleaf(ctx, nameref, K"BindingId", var_id=id)
end

# Assign `ex` to an SSA variable.
Expand Down
8 changes: 4 additions & 4 deletions src/desugaring.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ end

struct DesugaringContext{GraphType} <: AbstractLoweringContext
graph::GraphType
next_var_id::Ref{VarId}
bindings::Bindings
scope_layers::Vector{ScopeLayer}
mod::Module
end
Expand All @@ -21,9 +21,9 @@ function DesugaringContext(ctx)
source=SourceAttrType,
value=Any, name_val=String,
scope_type=Symbol, # :hard or :soft
var_id=VarId,
var_id=IdTag,
lambda_info=LambdaInfo)
DesugaringContext(graph, ctx.next_var_id, ctx.scope_layers, ctx.current_layer.mod)
DesugaringContext(graph, ctx.bindings, ctx.scope_layers, ctx.current_layer.mod)
end

# Flatten nested && or || nodes and expand their children
Expand Down Expand Up @@ -691,7 +691,7 @@ function expand_forms_2(ctx::DesugaringContext, ex::SyntaxTree, docs=nothing)
else
if k == K"="
@chk numchildren(ex) == 2
if kind(ex[1]) KSet"Identifier Placeholder SSAValue"
if kind(ex[1]) KSet"Identifier Placeholder BindingId"
TODO(ex, "destructuring assignment")
end
end
Expand Down
18 changes: 9 additions & 9 deletions src/eval.jl
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ end

# Convert SyntaxTree to the CodeInfo+Expr data stuctures understood by the
# Julia runtime
function to_code_info(ex, mod, funcname, nargs, var_info, slot_rewrites)
function to_code_info(ex, mod, funcname, nargs, bindings, slot_rewrites)
input_code = children(ex)
code = Any[to_lowered_expr(mod, var_info, ex) for ex in input_code]
code = Any[to_lowered_expr(mod, bindings, ex) for ex in input_code]

debuginfo = ir_debug_info(ex)

Expand All @@ -115,7 +115,7 @@ function to_code_info(ex, mod, funcname, nargs, var_info, slot_rewrites)
slot_rename_inds = Dict{String,Int}()
slotflags = Vector{UInt8}(undef, nslots)
for (id,i) in slot_rewrites
info = var_info[id]
info = lookup_binding(bindings, id)
name = info.name
ni = get(slot_rename_inds, name, 0)
slot_rename_inds[name] = ni + 1
Expand Down Expand Up @@ -176,7 +176,7 @@ function to_code_info(ex, mod, funcname, nargs, var_info, slot_rewrites)
)
end

function to_lowered_expr(mod, var_info, ex)
function to_lowered_expr(mod, bindings, ex)
k = kind(ex)
if is_literal(k) || k == K"Bool"
ex.value
Expand All @@ -203,7 +203,7 @@ function to_lowered_expr(mod, var_info, ex)
elseif k == K"SSAValue"
Core.SSAValue(ex.var_id)
elseif k == K"return"
Core.ReturnNode(to_lowered_expr(mod, var_info, ex[1]))
Core.ReturnNode(to_lowered_expr(mod, bindings, ex[1]))
elseif is_quoted(k)
if k == K"inert"
ex[1]
Expand All @@ -215,7 +215,7 @@ function to_lowered_expr(mod, var_info, ex)
"top-level scope" :
"none" # FIXME
nargs = length(ex.lambda_info.args)
ir = to_code_info(ex[1], mod, funcname, nargs, var_info, ex.slot_rewrites)
ir = to_code_info(ex[1], mod, funcname, nargs, bindings, ex.slot_rewrites)
if ex.lambda_info.is_toplevel_thunk
Expr(:thunk, ir)
else
Expand All @@ -226,7 +226,7 @@ function to_lowered_expr(mod, var_info, ex)
elseif k == K"goto"
Core.GotoNode(ex[1].id)
elseif k == K"gotoifnot"
Core.GotoIfNot(to_lowered_expr(mod, var_info, ex[1]), ex[2].id)
Core.GotoIfNot(to_lowered_expr(mod, bindings, ex[1]), ex[2].id)
else
# Allowed forms according to https://docs.julialang.org/en/v1/devdocs/ast/
#
Expand All @@ -243,7 +243,7 @@ function to_lowered_expr(mod, var_info, ex)
if isnothing(head)
TODO(ex, "Unhandled form for kind $k")
end
Expr(head, map(e->to_lowered_expr(mod, var_info, e), children(ex))...)
Expr(head, map(e->to_lowered_expr(mod, bindings, e), children(ex))...)
end
end

Expand All @@ -259,7 +259,7 @@ function Core.eval(mod::Module, ex::SyntaxTree)
return x
end
linear_ir = lower(mod, ex)
expr_form = to_lowered_expr(mod, linear_ir.var_info, linear_ir)
expr_form = to_lowered_expr(mod, linear_ir.bindings, linear_ir)
eval(mod, expr_form)
end

Expand Down
2 changes: 2 additions & 0 deletions src/kinds.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ function _insert_kinds()
"loopinfo"
# Identifier for a value which is only assigned once
"SSAValue"
# Unique identifying integer for bindings (of variables, constants, etc)
"BindingId"
# Scope expressions `(hygienic_scope ex s)` mean `ex` should be
# interpreted as being in scope `s`.
"hygienic_scope"
Expand Down
Loading

0 comments on commit 92c2197

Please sign in to comment.