-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
v1.12 throws UndefVarError: not defined in local scope
for variable renamed in function before being captured by a closure
#57141
Comments
Reproduction: julia> module Repro
struct BackIRType
value::Int
id::Int
BackIRType(value::Int) = new(value)
end
function assign_node_ids(node)
return assign_node_ids(node, 0)[1]
end
function assign_node_ids(node, starting_id::Int)
ii = starting_id
function _traverse(φ::BackIRType)
new_φ = _copy_and_assign_id(φ, ii)
if new_φ !== φ
ii += 1
end
return new_φ
end
_traverse(n) = n
return (_traverse(node), ii)
end
function _copy_and_assign_id(φ::BackIRType, id::Int)
isdefined(φ, :id) && return φ
return BackIRType(φ.value, id)
end
end
Main.Repro
julia> Repro.assign_node_ids(Repro.BackIRType(1))
ERROR: UndefVarError: `ii` not defined in local scope
Suggestion: check for an assignment to a local variable that shadows a global of the same name.
Stacktrace:
[1] (::Main.Repro.var"#_traverse#assign_node_ids##0")(φ::Main.Repro.BackIRType)
@ Main.Repro ./REPL[1]:16
[2] assign_node_ids
@ ./REPL[1]:25 [inlined]
[3] assign_node_ids(node::Main.Repro.BackIRType)
@ Main.Repro ./REPL[1]:10
[4] top-level scope
@ REPL[2]:1
julia> VERSION
v"1.12.0-DEV.1903" |
tagging @Keno since this seems probably related to variable world age changes |
I was able to shrink the reproducer to this: julia> repro() = f()[1]
repro (generic function with 1 method)
julia> function f()
i = 0
function increment_i()
i += 1
end
return (increment_i(), i)
end
f (generic function with 1 method)
julia> repro()
ERROR: UndefVarError: `i` not defined in local scope For reference, here is the emitted code for Julia 1.11.3, where things work as intended: julia> @code_typed repro()
CodeInfo(
1 ─ %1 = %new(Core.Box)::Core.Box
│ Core.setfield!(%1, :contents, 0)::Int64
│ %3 = %new(Main.:(var"#increment_i#12"), %1)::var"#increment_i#12"
│ %4 = invoke %3()::Any
│ %5 = Core.isdefined(%1, :contents)::Bool
└── goto #3 if not %5
2 ─ Core.getfield(%1, :contents)::Any
└── goto #4
3 ─ $(Expr(:throw_undef_if_not, :i, false))::Any
└── unreachable
4 ─ return %4
) => Any The This bug is not specific to boxed variables, it also happens with a manually constructed julia> repro2() = f2()[1]
repro2 (generic function with 1 method)
julia> function f2()
ref = Ref{Any}()
ref[] = 0
inner() = ref[] + 1
@assert isdefined(ref, :x)
return (inner(), ref[])
end
f2 (generic function with 1 method)
julia> repro2()
ERROR: UndefRefError: access to undefined reference With the same behavior, |
Further troubleshooting reveals that it is the SRoA pass which discards the call to infil> ir
29 1 ─ %1 = %new(Base.RefValue{Any})::Base.RefValue{Any} │╻╷╷ f2
│ builtin Base.setfield!(%1, :x, 0)::Int64
⋮
infil> sroa_mutables!(ir, defuses, used_ssas, lazydomtree, inlining)
infil> ir
29 1 ─ %1 = %new(Base.RefValue{Any})::Base.RefValue{Any} │╻╷╷ f2
│ nothing::Int64
⋮ whereas I believe it should conservatively abort in the case of the mutable @aviatesk Any thoughts on how/where to proceed with this, if this sounds correct to you? I'm thinking conceptually we might want to walk through definitions for arguments to any encountered |
That’s a great observation. As you pointed out, this is likely an issue with the SROA pass, specifically within More concretely, julia/Compiler/src/ssair/passes.jl Lines 1890 to 1899 in eff8ba4
|
As you said there might be an issue with the calculation of julia/Compiler/src/ssair/passes.jl Lines 1718 to 1728 in eff8ba4
|
I inspected the julia/Compiler/src/ssair/passes.jl Lines 1168 to 1176 in 575d8e8
In particular, walking with this collector does skip the |
Yeah, I also think it's not that the PR caused the issue, but more like it just exposed a problem that was already there. We probably need to further tweak the walker callback, or maybe we could also change the condition that sets |
Would a julia/Compiler/src/ssair/passes.jl Lines 16 to 24 in f209eba
Currently it seems like no use other than julia/Compiler/src/ssair/passes.jl Lines 1403 to 1422 in f209eba
But if |
If the use is opaque, it doesn't need to be handled, because it's supposed to be caught by the count comparison |
Indeed, so the count comparison takes care of it correctly but the |
thanks for the quick response on this! 🎉 |
I've not yet been able to create a minimal reproduction of this that doesn't depend on a private package (sorry), buti can at least start a bisect to try to find the offending commit.[Edit: better repro in message below]
In a private package, which has a function like
Calling the function like
throws
The error disappears if the function is marked
@noinline
or the variable isn't renamed in the function, i.e.or
The text was updated successfully, but these errors were encountered: