After #22210, tests of ACME.jl end in a crash (ref. https://github.com/JuliaLang/julia/pull/22210#issuecomment-314416531), reproducible with
Pkg.add("ACME", v"0.4.1", v"0.4.1+")
Pkg.test("ACME")
As Pkg.test("ACME") takes quite long, the much quicker repro is
using ACME
for i in 1:10000
@show i
mats = ACME.model_matrices(Circuit(), 1)
end
The iteration in which the crash occurs and the exact error and backtrace vary, but a value whose type is not a type at all seems to be a recurring theme. However, adding a gc() to model_matrices(...) with
diff --git a/src/ACME.jl b/src/ACME.jl
index 466a26d..ab7fd74 100644
--- a/src/ACME.jl
+++ b/src/ACME.jl
@@ -509,6 +509,7 @@ function model_matrices(circ::Circuit, t::Rational{BigInt})
res[v] = squeeze(res[v], 2)
end
+ gc()
p = [pv(circ) pi(circ) px(circ)//2+pxd(circ)//t pq(circ)]
if normsquared(p * indeterminates) > 1e-20
warn("Model output depends on indeterminate quantity")
consistently leads to a crash in iteration 1. The crash here actually occurs when trying to show a MethodError, this is an excerpt from a GDB session:
#12 0x00007ffff757b6af in jl_apply_generic (args=0x7fffffffa620, nargs=2) at ./src/gf.c:1928
1928 jl_value_t *res = jl_call_method_internal(mfunc, args, nargs);
(gdb) call jl_(args[0])
getfield(Base, Symbol("##494#495")){Base.MethodError}(ex=Base.MethodError(f=typeof(Base.:(*))(), args=<?#0x7fffe8f76990::Array{UInt64, 1}[0xffffffffffffffff]>, world=0x0000000000005675))
Note the garbled inner args, where the type is an array _instance_ (and trying do to something with that then hits an assertion further down the backtrace), hence my guess that this is a use-after-free.
My very wild guess is that the new inlining heuristics due to #22210 lead to code that somehow confuses the GC. But I'm way over my head here. If there is anything I can do to obtain/provide more information, I'm all ears.
The GC is not being confused, the codegen is.
The object being free'd seems to be indeterminates::Matrix{Base.Rational{Base.GMP.BigInt}} and it's use a few lines below is not being handled properly at the gc() callsite.
I've verified that the GC frame does not contain the root and from the typed ast it seems that it should.
The frame at the GC callsite is
(rr) p *$1->pgcstack
$20 = {nroots = 28, prev = 0x7fff81571088}
(rr) x/14a (((void**)($1->pgcstack)) + 2)
0x7fff81570f58: 0x7fed147610b0 0x7fed14c07050
0x7fff81570f68: 0x7fed152fddc0 0x7fed15f6c560
0x7fff81570f78: 0x7fed152fdb40 0x7fed14d60a90
0x7fff81570f88: 0x7fed17050160 0x7fed1547f9d0
0x7fff81570f98: 0x7fed1467fcd0 0x0
0x7fff81570fa8: 0x0 0x0
0x7fff81570fb8: 0x0 0x0
while the object that should be rooted is 0x7fed160ca4f0.
Thanks for taking a look at this so quickly! One observation I can add: By making the type of p and indeterminates inferable (by adding type asserts), the problem goes away. But whether this actually leads to proper rooting or just by chance happens to prevent overwriting anything critical I don't know.
I understand what's happening here. Thinking about a fix.
Standalone reproducer:
was_gced = false
@noinline make_tuple(x) = tuple(x)
@noinline use(x) = ccall(:jl_breakpoint, Void, ())
@noinline assert_not_gced() = @assert !was_gced
function foo()
b = Ref(2)
finalizer(b, x->(global was_gced; was_gced=true))
y = make_tuple(b)
x = y[1]
a = Ref(1)
use(x); use(a); use(y)
c = Ref(3)
gc(); assert_not_gced();
use(x)
use(c)
end
foo()
What's happening is that at the first use(x), it sees that y is live, and since y contains x and y is immutable, it decides x and a can share a slot (since a is dead after it's use). The problem is that it still writes x into the slot right after it is defined, so the definition of a overwrites it. What needs to happen instead is that it needs to root x after y goes out of scope.
Should be fixed by https://github.com/JuliaLang/julia/pull/22830. Nice find.