fix false-positive `multiloc` in AllocOpt escape analysis (#61131)
discovered via automated Claude audit for typos and other minor /
obvious bugs. I manually reviewed each result
if anybody is getting annoyed at the amount of AI code I am pushing
please let me know! it seems to be doing a good job and I am doing my
best to clean it all up + be camera ready, but I just want to double
check.
example benchmark:
```
julia> using BenchmarkTools
julia> mutable struct AnyBox
val::Any
end
julia> function preserve_any(x)
b = AnyBox(x)
GC.@preserve b begin
return b.val
end
end
julia> function loop(n)
s = "v"
for _ in 1:n
s = preserve_any(s)::String
end
s
end
julia> @benchmark loop(10)
### master
BenchmarkTools.Trial: 10000 samples with 996 evaluations per sample.
Range (min … max): 25.979 ns … 1.413 μs ┊ GC (min … max): 0.00% … 96.22%
Time (median): 28.908 ns ┊ GC (median): 0.00%
Time (mean ± σ): 31.487 ns ± 24.859 ns ┊ GC (mean ± σ): 6.74% ± 9.68%
▃█▅▃ ▁
█████▇▇▆▆▅▅▅▅▁▄▃▄▃▁▃▁▁▅▁▁▁▃▄▃▁▁▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▄▅▆▇▆▆▆ █
26 ns Histogram: log(frequency) by time 134 ns <
Memory estimate: 144 bytes, allocs estimate: 9.
### PR
BenchmarkTools.Trial: 10000 samples with 1000 evaluations per sample.
Range (min … max): 2.084 ns … 45.042 ns ┊ GC (min … max): 0.00% … 0.00%
Time (median): 2.167 ns ┊ GC (median): 0.00%
Time (mean ± σ): 2.204 ns ± 0.560 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
▂ █ ▆ ▅ ▂ ▂ ▁
▃▁▁▁▁▁▁█▁▁▁▁▁▁█▁▁▁▁▁▁█▁▁▁▁▁▁█▁▁▁▁▁▁▁█▁▁▁▁▁▁█▁▁▁▁▁▁█▁▁▁▁▁▁▆ █
2.08 ns Histogram: log(frequency) by time 2.42 ns <
Memory estimate: 0 bytes, allocs estimate: 0.
```
this is technically two different bugfixes in one. claude found the `?`
precedence one first, then found the other while attempting to write a
test for it.
description:
Fix a false-positive `multiloc` flag in `addMemOp` that prevented
AllocOptPass from stack-promoting any allocation with GC-pointer fields.
The `hasobjref` flag is initialized to `false` in the Field constructor
but only set to `true` after the `multiloc` check, so the first access
to a new objref field always triggered `multiloc = true`. Guard the
check with `!field.second.accesses.empty()` so it only fires when there
is a prior access with a different ref/bits kind.
This caused AllocOptPass to bail with "unusual object reference" on
every non-escaping mutable struct with a reference-typed field (Any,
String, Vector, etc.), blocking both `splitOnStack` and `moveToStack`.
Also fix an operator precedence bug in the atomicrmw/cmpxchg escape
check: `!=`bound tighter than `?:`
https://github.com/JuliaLang/julia/blob/74b7adc233b6a23a8f38eedd3830ee99cc6ac161/src/llvm-alloc-helpers.cpp#L321
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>