Skip to content

Commit 64c5235

Browse files
mlechutopolarity
andcommitted
Code review comments
Co-authored-by: Cody Tapscott <topolarity@tapscott.me>
1 parent ca4155d commit 64c5235

File tree

3 files changed

+52
-35
lines changed

3 files changed

+52
-35
lines changed

JuliaLowering/src/bindings.jl

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ mutable struct BindingInfo
55
const id::IdTag # Unique integer identifying this binding
66
const name::String
77
const kind::Symbol # :local :global :argument :static_parameter
8-
const node_id::Int # ID of associated K"BindingId" node in the syntax graph
8+
const node_id::Int # ID of some K"BindingId" node in the syntax graph
99
const mod::Union{Nothing,Module} # Set when `kind === :global`
1010
type::Union{Nothing,SyntaxTree} # Type, for bindings declared like x::T = 10
1111
is_const::Bool # Constant, cannot be reassigned
@@ -47,23 +47,23 @@ end
4747

4848
function Base.show(io::IO, binfo::BindingInfo)
4949
print(io, "BindingInfo(", binfo.id,
50-
", name=", repr(binfo.name),
50+
", ", repr(binfo.name),
5151
", ", repr(binfo.kind),
52-
", node=", binfo.node_id)
52+
", ", binfo.node_id)
5353
!isnothing(binfo.mod) && print(io, ", mod=", binfo.mod)
5454
!isnothing(binfo.type) && print(io, ", type=", binfo.type)
55-
binfo.is_const && print(io, ", is_const")
56-
binfo.is_ssa && print(io, ", is_ssa")
57-
binfo.is_internal && print(io, ", is_internal")
58-
binfo.is_ambiguous_local && print(io, ", is_ambiguous_local")
59-
binfo.is_nospecialize && print(io, ", is_nospecialize")
60-
binfo.is_read && print(io, ", is_read")
61-
binfo.is_called && print(io, ", is_called")
62-
binfo.is_assigned && print(io, ", is_assigned")
63-
binfo.is_assigned_once && print(io, ", is_assigned_once")
64-
binfo.is_captured && print(io, ", is_captured")
65-
binfo.is_always_defined && print(io, ", is_always_defined")
66-
binfo.is_used_undef && print(io, ", is_used_undef")
55+
binfo.is_const && print(io, ", is_const=true")
56+
binfo.is_ssa && print(io, ", is_ssa=true")
57+
binfo.is_internal && print(io, ", is_internal=true")
58+
binfo.is_ambiguous_local && print(io, ", is_ambiguous_local=true")
59+
binfo.is_nospecialize && print(io, ", is_nospecialize=true")
60+
binfo.is_read && print(io, ", is_read=true")
61+
binfo.is_called && print(io, ", is_called=true")
62+
binfo.is_assigned && print(io, ", is_assigned=true")
63+
binfo.is_assigned_once && print(io, ", is_assigned_once=true")
64+
binfo.is_captured && print(io, ", is_captured=true")
65+
binfo.is_always_defined && print(io, ", is_always_defined=true")
66+
binfo.is_used_undef && print(io, ", is_used_undef=true")
6767
print(io, ")")
6868
end
6969

@@ -158,9 +158,7 @@ function binding_ex(ctx::AbstractLoweringContext, b::BindingInfo)
158158
end
159159
binding_ex(ctx, id::IdTag) = binding_ex(ctx, get_binding(ctx, id))
160160

161-
# One lambda's variables. TODO: It might be easier to use scope ID as a lambda
162-
# ID and give BindingInfo a field noting which lambda it belongs to. This could
163-
# probably just be a Set{IdTag} of captures.
161+
# One lambda's variables
164162
struct LambdaBindings
165163
# Binding ID of #self#
166164
self::IdTag
@@ -169,6 +167,10 @@ struct LambdaBindings
169167
# A map from every referenced local binding ID to whether the local is
170168
# captured (true) or native to this lambda (false). References in inner
171169
# lambdas count: `inner.locals_capt[id]` implies `haskey(locals_capt, id)`
170+
# TODO: If we use scope ID as a lambda ID and give BindingInfo a field
171+
# noting which lambda it belongs to, we could just make this a BitSet of
172+
# vars present, where we tell if a binding is captured by comparing
173+
# this.scope_id with the BindingInfo's scope_id.
172174
locals_capt::Dict{IdTag,Bool}
173175
end
174176

JuliaLowering/src/scope_analysis.jl

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,6 @@ enclosing_lambda(ctx, scope::ScopeInfo) = ctx.scopes[scope.lambda_id]
9595
parent(ctx, scope::ScopeInfo) = is_top_scope(scope) ? nothing :
9696
ctx.scopes[scope.parent_id]
9797

98-
enclosing_lambda(ctx, lb::LambdaBindings) = ctx.scopes[lb.scope_id]
99-
10098
_var_str(v) = v === :local ? "local variable" :
10199
v === :global ? "global variable" :
102100
v === :argument ? "argument" :
@@ -112,7 +110,7 @@ function maybe_declare_in_scope!(ctx, scope::ScopeInfo, ex, new_k::Symbol)
112110
if kind(ex) === K"BindingId"
113111
bid = ex.var_id
114112
@assert get_binding(ctx, bid).kind === new_k
115-
record_lambda_var!(ctx, scope, get_binding(ctx, bid), false)
113+
record_lambda_var!(ctx, scope, get_binding(ctx, bid), capt=false)
116114
return bid
117115
elseif kind(ex) === K"Placeholder"
118116
return nothing
@@ -130,7 +128,7 @@ function maybe_declare_in_scope!(ctx, scope::ScopeInfo, ex, new_k::Symbol)
130128
elseif old_k === new_k
131129
(new_k === :global || new_k === :local) && return bid
132130
throw(LoweringError(ex, "function $(_var_str(new_k)) name not unique"))
133-
# See note in test/scopes.jl
131+
# See note in test/scopes.jl: "globals may overlap args or sparams"
134132
# elseif new_k === :global && old_k in (:argument, :static_parameter)
135133
# declare_in_scope!(ctx, scope, ex, :global)
136134
else
@@ -140,26 +138,27 @@ function maybe_declare_in_scope!(ctx, scope::ScopeInfo, ex, new_k::Symbol)
140138
end
141139
end
142140

141+
# globals are added to both `scope` and the top scope
143142
function declare_in_scope!(ctx, scope::ScopeInfo, ex, bk::Symbol; kws...)
144143
nk = NameKey(ex)
145144
if bk === :global
146-
home_scope = top_scope(ctx)
145+
declaration_scope = top_scope(ctx)
147146
mod = ctx.scope_layers[ex.scope_layer].mod
148147
else
149-
home_scope = scope
148+
declaration_scope = scope
150149
mod = nothing
151150
end
152151
b = _new_binding(ctx, ex, nk.name, bk; mod, kws...)
153-
home_scope.vars[nk] = b.id
152+
declaration_scope.vars[nk] = b.id
154153
scope.vars[nk] = b.id
155154
@assert !haskey(enclosing_lambda(ctx, scope).locals_capt, b.id)
156-
record_lambda_var!(ctx, scope, b, false)
155+
record_lambda_var!(ctx, scope, b, capt=false)
157156
return b.id
158157
end
159158

160159
# If `b` is local and not yet recorded in the lambda bindings, mark it as
161160
# `capt`. Also, (if `capt==true`), add it to any parent lambdas.
162-
function record_lambda_var!(ctx, scope::Union{ScopeInfo, LambdaBindings}, b, capt)
161+
function record_lambda_var!(ctx, scope::ScopeInfo, b; capt)
163162
if b.kind === :global || b.is_ssa
164163
return
165164
end
@@ -169,7 +168,7 @@ function record_lambda_var!(ctx, scope::Union{ScopeInfo, LambdaBindings}, b, cap
169168
b.is_captured = capt
170169
s2 = parent(ctx, lam)
171170
if capt && !isnothing(s2)
172-
record_lambda_var!(ctx, s2, b, true)
171+
record_lambda_var!(ctx, s2, b, capt=true)
173172
end
174173
end
175174
end
@@ -262,7 +261,9 @@ function enter_scope!(ctx, ex)
262261
#---------------------------------------------------------------------------
263262
# Find assignment targets, possibly introducing implicit locals and globals
264263
for (bid, node_id) in sort!(collect(scope.binding_assignments))
265-
# mutable nameless bindings may be introduced in desugaring
264+
# Mutable nameless bindings may be introduced in desugaring. These
265+
# should be capturable, and may be local to the nearest lambda or
266+
# global. Desugaring should ensure these are never used undef.
266267
maybe_declare_in_scope!(ctx, scope, SyntaxTree(ctx.graph, node_id),
267268
get_binding(ctx, bid).kind)
268269
end
@@ -302,7 +303,7 @@ function enter_scope!(ctx, ex)
302303
throw(LoweringError(ex, "cannot overwrite a static parameter"))
303304
elseif b.kind === :local || b.kind === :argument
304305
# unambiguous assignment to existing variable
305-
record_lambda_var!(ctx, scope, b, true)
306+
record_lambda_var!(ctx, scope, b, capt=true)
306307
end
307308
end
308309

@@ -320,8 +321,10 @@ function add_local_decls!(ctx, stmts, srcref, scope)
320321
end
321322
end
322323

323-
function _resolve_scopes(ctx, ex::SyntaxTree, @nospecialize(scope))
324+
function _resolve_scopes(ctx, ex::SyntaxTree,
325+
@nospecialize(scope::Union{Nothing, ScopeInfo}))
324326
k = kind(ex)
327+
@assert scope isa ScopeInfo || k === K"lambda"
325328
if k == K"Identifier"
326329
b = resolve_name(ctx, ex)
327330
# Unresolved names are assumed global
@@ -330,10 +333,10 @@ function _resolve_scopes(ctx, ex::SyntaxTree, @nospecialize(scope))
330333
b = get_binding(ctx, gid)
331334
end
332335
# Locals not present in the current lambda need capturing
333-
record_lambda_var!(ctx, scope, b, true)
336+
record_lambda_var!(ctx, scope, b, capt=true)
334337
newleaf(ctx, ex, K"BindingId", b.id)
335338
elseif k === K"BindingId"
336-
record_lambda_var!(ctx, scope, get_binding(ctx, ex), true)
339+
record_lambda_var!(ctx, scope, get_binding(ctx, ex), capt=true)
337340
ex
338341
elseif k == K"softscope"
339342
makeleaf(ctx, ex, K"TOMBSTONE")
@@ -494,6 +497,8 @@ function _resolve_scopes(ctx, ex::SyntaxTree, @nospecialize(scope))
494497
@assert kind(resolved[1]) === K"BindingId"
495498
if get_binding(ctx, resolved[1].var_id).kind === :local
496499
throw(LoweringError(ex, "unsupported `const` declaration on local variable"))
500+
elseif !is_top_scope(enclosing_lambda(ctx, scope))
501+
throw(LoweringError(ex, "unsupported `const` inside function"))
497502
end
498503
resolved
499504
elseif k == K"assign_or_constdecl_if_global"
@@ -590,7 +595,8 @@ function analyze_variables!(ctx, ex)
590595
b.is_read = true
591596
# The type of typed locals is invisible in the previous pass,
592597
# but is filled in here.
593-
record_lambda_var!(ctx, ctx.lambda_bindings, b, true)
598+
scope = ctx.scopes[ctx.lambda_bindings.scope_id]
599+
record_lambda_var!(ctx, scope, b, capt=true)
594600
@assert b.kind === :global || b.is_ssa || haskey(ctx.lambda_bindings.locals_capt, b.id)
595601
elseif k == K"Identifier"
596602
@assert false
@@ -611,7 +617,8 @@ function analyze_variables!(ctx, ex)
611617
if kind(lhs) != K"Placeholder"
612618
b = get_binding(ctx, lhs)
613619
add_assign!(b)
614-
record_lambda_var!(ctx, ctx.lambda_bindings, b, true)
620+
scope = ctx.scopes[ctx.lambda_bindings.scope_id]
621+
record_lambda_var!(ctx, scope, b, capt=true)
615622
if !isnothing(b.type)
616623
# Assignments introduce a variable's type later during closure
617624
# conversion, but we must model that explicitly here.

JuliaLowering/test/decls_ir.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,14 @@ let
238238
# └────┘ ── unsupported `const` declaration on local variable
239239
end
240240

241+
########################################
242+
# Error: Const not supported in function scope
243+
function (); global g; const g = 1; end
244+
#---------------------
245+
LoweringError:
246+
function (); global g; const g = 1; end
247+
# └────┘ ── unsupported `const` inside function
248+
241249
########################################
242250
# Type decl on function argument
243251
function f(x)

0 commit comments

Comments
 (0)