Skip to content
Open
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
10 changes: 8 additions & 2 deletions JuliaLowering/src/ast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Unique symbolic identity for a variable, constant, label, or other entity
const IdTag = Int

"""
Id for scope layers in macro expansion
Id for hygienic scope layers in macro expansion
"""
const LayerId = Int

Expand All @@ -85,6 +85,11 @@ struct ScopeLayer
is_macro_expansion::Bool # FIXME
end

"""
Lexical scope ID
"""
const ScopeId = Int

#-------------------------------------------------------------------------------
# AST creation utilities
_node_id(graph::SyntaxGraph, ex::SyntaxTree) = (check_compatible_graph(graph, ex); ex._id)
Expand Down Expand Up @@ -171,7 +176,8 @@ function newleaf(ctx, srcref, k::Kind, @nospecialize(value))
setattr!(leaf._graph, leaf._id, :id, value)
elseif k == K"symbolic_label"
setattr!(leaf._graph, leaf._id, :name_val, value)
elseif k in KSet"TOMBSTONE SourceLocation latestworld latestworld_if_toplevel"
elseif k in KSet"TOMBSTONE SourceLocation latestworld latestworld_if_toplevel
use_softscope_if_toplevel"
# no attributes
else
val = k == K"Integer" ? convert(Int, value) :
Expand Down
237 changes: 83 additions & 154 deletions JuliaLowering/src/bindings.jl
Original file line number Diff line number Diff line change
@@ -1,74 +1,69 @@
"""
Metadata about a binding
"""
struct BindingInfo
id::IdTag # Unique integer identifying this binding
name::String
kind::Symbol # :local :global :argument :static_parameter
node_id::Int # ID of associated K"BindingId" node in the syntax graph
mod::Union{Nothing,Module} # Set when `kind === :global`
mutable struct BindingInfo
const id::IdTag # Unique integer identifying this binding
const name::String
const kind::Symbol # :local :global :argument :static_parameter
const node_id::Int # ID of associated K"BindingId" node in the syntax graph
const mod::Union{Nothing,Module} # Set when `kind === :global`
type::Union{Nothing,SyntaxTree} # Type, for bindings declared like x::T = 10
n_assigned::Int32 # Number of times variable is assigned to
is_const::Bool # Constant, cannot be reassigned
is_ssa::Bool # Single assignment, defined before use
is_captured::Bool # Variable is captured by some lambda
is_always_defined::Bool # A local that we know has an assignment that dominates all usages (is never undef)
is_internal::Bool # True for internal bindings generated by the compiler
is_ambiguous_local::Bool # Local, but would be global in soft scope (ie, the REPL)
is_nospecialize::Bool # @nospecialize on this argument (only valid for kind == :argument)

# flisp: vinfo
is_nospecialize::Bool # @nospecialize on this argument (only valid for kind == :argument)
is_read::Bool
is_called::Bool
is_assigned::Bool # the implicit assignment to arguments doesn't count
is_assigned_once::Bool
is_captured::Bool
is_always_defined::Bool
is_used_undef::Bool
end

function BindingInfo(id::IdTag, name::AbstractString, kind::Symbol, node_id::Integer;
mod::Union{Nothing,Module} = nothing,
type::Union{Nothing,SyntaxTree} = nothing,
n_assigned::Integer = 0,
is_const::Bool = false,
is_ssa::Bool = false,
is_captured::Bool = false,
is_always_defined::Bool = is_ssa,
is_internal::Bool = false,
is_ambiguous_local::Bool = false,
is_nospecialize::Bool = false)
BindingInfo(id, name, kind, node_id, mod, type, n_assigned, is_const,
is_ssa, is_captured, is_always_defined,
is_internal, is_ambiguous_local, is_nospecialize)
is_nospecialize::Bool = false,
is_read::Bool = false,
is_called::Bool = false,
is_assigned::Bool = false,
is_assigned_once::Bool = false,
is_captured::Bool = false,
is_always_defined::Bool = is_ssa || kind === :argument,
is_used_undef::Bool = false)
BindingInfo(id, name, kind, node_id, mod, type, is_const, is_ssa,
is_internal, is_ambiguous_local, is_nospecialize, is_read,
is_called, is_assigned, is_assigned_once, is_captured,
is_always_defined, is_used_undef)
end

function Base.show(io::IO, binfo::BindingInfo)
print(io, "BindingInfo(", binfo.id, ", ",
repr(binfo.name), ", ",
repr(binfo.kind), ", ",
binfo.node_id)
if !isnothing(binfo.mod)
print(io, ", mod=", binfo.mod)
end
if !isnothing(binfo.type)
print(io, ", type=", binfo.type)
end
if binfo.n_assigned != 0
print(io, ", n_assigned=", binfo.n_assigned)
end
if binfo.is_const
print(io, ", is_const=", binfo.is_const)
end
if binfo.is_ssa
print(io, ", is_ssa=", binfo.is_ssa)
end
if binfo.is_captured
print(io, ", is_captured=", binfo.is_captured)
end
if binfo.is_always_defined != binfo.is_ssa
print(io, ", is_always_defined=", binfo.is_always_defined)
end
if binfo.is_internal
print(io, ", is_internal=", binfo.is_internal)
end
if binfo.is_ambiguous_local
print(io, ", is_ambiguous_local=", binfo.is_ambiguous_local)
end
if binfo.is_nospecialize
print(io, ", is_nospecialize=", binfo.is_nospecialize)
end
print(io, "BindingInfo(", binfo.id,
", name=", repr(binfo.name),
", ", repr(binfo.kind),
", node=", binfo.node_id)
!isnothing(binfo.mod) && print(io, ", mod=", binfo.mod)
!isnothing(binfo.type) && print(io, ", type=", binfo.type)
binfo.is_const && print(io, ", is_const")
binfo.is_ssa && print(io, ", is_ssa")
binfo.is_internal && print(io, ", is_internal")
binfo.is_ambiguous_local && print(io, ", is_ambiguous_local")
binfo.is_nospecialize && print(io, ", is_nospecialize")
binfo.is_read && print(io, ", is_read")
binfo.is_called && print(io, ", is_called")
binfo.is_assigned && print(io, ", is_assigned")
binfo.is_assigned_once && print(io, ", is_assigned_once")
binfo.is_captured && print(io, ", is_captured")
binfo.is_always_defined && print(io, ", is_always_defined")
binfo.is_used_undef && print(io, ", is_used_undef")
print(io, ")")
end

Expand Down Expand Up @@ -106,147 +101,81 @@ function _binding_id(ex::SyntaxTree)
ex.var_id
end

function update_binding!(bindings::Bindings, x;
type=nothing, is_const=nothing, add_assigned=0,
is_always_defined=nothing, is_captured=nothing)
id = _binding_id(x)
b = lookup_binding(bindings, id)
bindings.info[id] = BindingInfo(
b.id,
b.name,
b.kind,
b.node_id,
b.mod,
isnothing(type) ? b.type : type,
b.n_assigned + add_assigned,
isnothing(is_const) ? b.is_const : is_const,
b.is_ssa,
isnothing(is_captured) ? b.is_captured : is_captured,
isnothing(is_always_defined) ? b.is_always_defined : is_always_defined,
b.is_internal,
b.is_ambiguous_local,
b.is_nospecialize
)
end

function lookup_binding(bindings::Bindings, x)
function get_binding(bindings::Bindings, x)
bindings.info[_binding_id(x)]
end

function lookup_binding(ctx::AbstractLoweringContext, x)
lookup_binding(ctx.bindings, x)
function get_binding(ctx::AbstractLoweringContext, x)
get_binding(ctx.bindings, x)
end

function update_binding!(ctx::AbstractLoweringContext, x; kws...)
update_binding!(ctx.bindings, x; kws...)
end

function new_binding(ctx::AbstractLoweringContext, srcref::SyntaxTree,
name::AbstractString, kind::Symbol; kws...)
function _new_binding(ctx::AbstractLoweringContext, srcref::SyntaxTree,
name::AbstractString, kind::Symbol; kws...)
binding_id = next_binding_id(ctx.bindings)
# A binding is only useful when it shows up in the tree, so create its tree
# node eagerly and share it among uses (see `binding_ex`)
ex = @ast ctx srcref binding_id::K"BindingId"
add_binding(ctx.bindings, BindingInfo(binding_id, name, kind, ex._id; kws...))
ex
b = BindingInfo(binding_id, name, kind, ex._id; kws...)
add_binding(ctx.bindings, b)
return b
end

# Create a new SSA binding
function ssavar(ctx::AbstractLoweringContext, srcref, name="tmp")
nameref = makeleaf(ctx, srcref, K"Identifier")
nameref.name_val = name
new_binding(ctx, nameref, name, :local; is_ssa=true, is_internal=true)
binding_ex(ctx, _new_binding(ctx, nameref, name, :local;
is_ssa=true, is_internal=true))
end

# Create a new local mutable binding or lambda argument
function new_local_binding(ctx::AbstractLoweringContext, srcref, name; kind=:local, kws...)
function new_local_binding(ctx::AbstractLoweringContext, srcref, name;
kind=:local, kws...)
@assert kind === :local || kind === :argument
nameref = makeleaf(ctx, srcref, K"Identifier")
nameref.name_val = name
ex = new_binding(ctx, nameref, name, kind; is_internal=true, kws...)
b = _new_binding(ctx, nameref, name, kind; is_internal=true, kws...)
lbindings = current_lambda_bindings(ctx)
if !isnothing(lbindings)
init_lambda_binding(lbindings, ex.var_id)
init_lambda_binding(lbindings, b.id, false)
end
ex
binding_ex(ctx, b)
end

function new_global_binding(ctx::AbstractLoweringContext, srcref, name, mod; kws...)
nameref = makeleaf(ctx, srcref, K"Identifier")
nameref.name_val = name
new_binding(ctx, nameref, name, :global; is_internal=true, mod=mod, kws...)
binding_ex(ctx, _new_binding(
ctx, nameref, name, :global; is_internal=true, mod=mod, kws...))
end

function binding_ex(ctx::AbstractLoweringContext, id::IdTag)
function binding_ex(ctx::AbstractLoweringContext, b::BindingInfo)
# Reconstruct the SyntaxTree for this binding. We keep only the node_id
# here, because that's got a concrete type. Whereas if we stored SyntaxTree
# that would contain the type of the graph used in the pass where the
# bindings were created and we'd need to call reparent(), etc.
SyntaxTree(syntax_graph(ctx), lookup_binding(ctx, id).node_id)
end


#-------------------------------------------------------------------------------
"""
Metadata about how a binding is used within some enclosing lambda
"""
struct LambdaBindingInfo
is_captured::Bool
is_read::Bool
is_assigned::Bool
# Binding was the function name in a call. Used for specialization
# heuristics in the optimizer.
is_called::Bool
end

LambdaBindingInfo() = LambdaBindingInfo(false, false, false, false)

function LambdaBindingInfo(parent::LambdaBindingInfo;
is_captured = nothing,
is_read = nothing,
is_assigned = nothing,
is_called = nothing)
LambdaBindingInfo(
isnothing(is_captured) ? parent.is_captured : is_captured,
isnothing(is_read) ? parent.is_read : is_read,
isnothing(is_assigned) ? parent.is_assigned : is_assigned,
isnothing(is_called) ? parent.is_called : is_called,
)
SyntaxTree(syntax_graph(ctx), b.node_id)
end
binding_ex(ctx, id::IdTag) = binding_ex(ctx, get_binding(ctx, id))

# One lambda's variables. TODO: It might be easier to use scope ID as a lambda
# ID and give BindingInfo a field noting which lambda it belongs to. This could
# probably just be a Set{IdTag} of captures.
struct LambdaBindings
# Bindings used within the lambda
# Binding ID of #self#
self::IdTag
bindings::Dict{IdTag,LambdaBindingInfo}
end

LambdaBindings(self::IdTag = 0) = LambdaBindings(self, Dict{IdTag,LambdaBindings}())

function init_lambda_binding(bindings::LambdaBindings, id; kws...)
@assert !haskey(bindings.bindings, id)
bindings.bindings[id] = LambdaBindingInfo(LambdaBindingInfo(); kws...)
end

function update_lambda_binding!(bindings::LambdaBindings, x; kws...)
id = _binding_id(x)
binfo = bindings.bindings[id]
bindings.bindings[id] = LambdaBindingInfo(binfo; kws...)
end

function update_lambda_binding!(ctx::AbstractLoweringContext, x; kws...)
update_lambda_binding!(current_lambda_bindings(ctx), x; kws...)
# For finding the parent lambda in variable analysis
scope_id::ScopeId
# A map from every referenced local binding ID to whether the local is
# captured (true) or native to this lambda (false). References in inner
# lambdas count: `inner.locals_capt[id]` implies `haskey(locals_capt, id)`
locals_capt::Dict{IdTag,Bool}
end

function lookup_lambda_binding(bindings::LambdaBindings, x)
get(bindings.bindings, _binding_id(x), nothing)
end

function lookup_lambda_binding(ctx::AbstractLoweringContext, x)
lookup_lambda_binding(current_lambda_bindings(ctx), x)
end

function has_lambda_binding(bindings::LambdaBindings, x)
haskey(bindings.bindings, _binding_id(x))
end
LambdaBindings(self::IdTag = 0, scope_id::ScopeId = 0) =
LambdaBindings(self, scope_id, Dict{IdTag,LambdaBindings}())

function has_lambda_binding(ctx::AbstractLoweringContext, x)
has_lambda_binding(current_lambda_bindings(ctx), x)
function init_lambda_binding(bindings::LambdaBindings, id::IdTag, capt::Bool)
@assert !haskey(bindings.locals_capt, id)
bindings.locals_capt[id] = capt
end
Loading