// Generated by Fuzzlyn v3.3 on 2026-05-16 17:33:26
// Run on Arm64 MacOS
// Seed: 12698610434432572438-vectort,vector64,vector128,armadvsimd,armadvsimdarm64,armaes,armarmbase,armarmbasearm64,armcrc32,armcrc32arm64,armdp,armrdm,armrdmarm64,armsha1,armsha256
// Reduced from 439.0 KiB to 7.3 KiB in 00:07:27
// Hits JIT assert for Release:
// Assertion failed '!cache->Lookup(fstruct) || ((*cache)[fstruct].Result == sameSelResult)' in 'Program:M1():short' during 'VN-based dead store removal' (IL size 1917; hash 0x3fe0537e; FullOpts)
//
// File: /Users/runner/work/1/s/src/coreclr/jit/valuenum.cpp Line: 3675
//
using System.Numerics;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;
public struct S0
{
public Vector<ulong> F0;
public ulong F1;
public ulong F2;
public uint F3;
public bool F4;
public Vector64<ulong> F5;
public short F6;
public int F7;
public sbyte F8;
public byte F9;
public S0(bool f4, int f7, sbyte f8, byte f9)
{
}
}
public struct S1
{
public Vector128<short> F0;
public bool F1;
public ulong F3;
public short F4;
public uint F5;
public S0 F7;
public S0 F8;
public sbyte F9;
public S1(Vector128<short> f0, ulong f3, Vector<sbyte> f6, S0 f7, S0 f8, sbyte f9)
{
F7 = f7;
F8 = f8;
}
}
public struct S2
{
public S0 F0;
public S0 F1;
public S0 F2;
public S2(S0 f0, S0 f1, S0 f2) : this()
{
F0 = f0;
F1 = f1;
F2 = f2;
}
}
public class C0
{
public Vector128<byte> F0;
public long F1;
public S2 F2;
public ushort F3;
public S1 F4;
public uint F6;
public C0(Vector128<byte> f0, long f1, S2 f2, ushort f3, S1 f4, sbyte f5, uint f6)
{
F0 = f0;
F2 = f2;
F3 = f3;
F4 = f4;
F6 = f6;
}
}
public class Program
{
public static long[][] s_5;
public static short s_7;
public static void Main()
{
M1();
}
public static short M1()
{
bool var0 = default(bool);
S0 var21 = default(S0);
ushort[] var31 = default(ushort[]);
if (var0)
{
var vr5 = (byte)0;
var vr9 = (sbyte)0;
C0 var12 = new C0(Vector128.CreateScalar(vr5), 0, new S2(new S0(false, 0, 0, 1), new S0(true, 0, 0, 0), new S0(false, 0, 0, 1)), 0, new S1(Vector128.Create<short>(0), 0, Vector128.CreateScalar(vr9).AsVector(), new S0(true, 0, 1, 0), new S0(false, 1, 0, 1), 0), 0, 0);
System.Console.WriteLine(var12.F1);
System.Console.WriteLine(var12.F2.F0.F1);
System.Console.WriteLine(var12.F2.F0.F2);
System.Console.WriteLine(var12.F2.F0.F4);
System.Console.WriteLine(var12.F2.F0.F5);
System.Console.WriteLine(var12.F2.F0.F8);
System.Console.WriteLine(var12.F2.F0.F9);
System.Console.WriteLine(var12.F2.F1.F1);
System.Console.WriteLine(var12.F2.F1.F6);
System.Console.WriteLine(var12.F2.F1.F8);
System.Console.WriteLine(var12.F2.F1.F9);
System.Console.WriteLine(var12.F2.F2.F0);
System.Console.WriteLine(var12.F2.F2.F2);
System.Console.WriteLine(var12.F2.F2.F4);
System.Console.WriteLine(var12.F2.F2.F7);
System.Console.WriteLine(var12.F2.F2.F9);
System.Console.WriteLine(var12.F4.F0);
System.Console.WriteLine(var12.F4.F3);
System.Console.WriteLine(var12.F4.F7.F1);
System.Console.WriteLine(var12.F4.F7.F2);
System.Console.WriteLine(var12.F4.F7.F4);
System.Console.WriteLine(var12.F4.F7.F6);
System.Console.WriteLine(var12.F4.F7.F8);
System.Console.WriteLine(var12.F4.F8.F0);
System.Console.WriteLine(var12.F4.F8.F1);
System.Console.WriteLine(var12.F4.F8.F3);
for (ulong lvar13 = 2UL; lvar13 > 0UL; lvar13--)
{
try
{
S2[] var14 = new S2[]
{
new S2(new S0(false, 0, 0, 0), new S0(false, 0, 0, 0), new S0(true, 0, 0, 0)),
new S2(new S0(false, 0, 0, 0), new S0(false, 0, 0, 0), new S0(false, 0, 0, 0)),
new S2(new S0(true, 0, 0, 0), new S0(true, 0, 1, 0), new S0(false, 0, 0, 0)),
new S2(new S0(false, 0, 1, 1), new S0(false, 0, 0, 0), new S0(false, 0, 0, 0)),
new S2(new S0(false, 0, 0, 0), new S0(true, 0, 0, 1), new S0(true, 0, 0, 1)),
new S2(new S0(false, 0, 0, 0), new S0(true, 0, 0, 0), new S0(true, 0, 0, 0)),
new S2(new S0(false, 0, 0, 0), new S0(false, 0, 0, 0), new S0(false, 0, 0, 0)),
new S2(new S0(false, 0, 0, 0), new S0(true, 0, 0, 0), new S0(true, 0, 0, 0)),
new S2(new S0(true, 0, 1, 1), new S0(true, 1, 0, 0), new S0(true, 0, 0, 0)),
new S2(new S0(false, 0, 0, 0), new S0(false, 0, 0, 0), new S0(true, 0, 1, 0))
};
if (!var14[0].F1.F4)
{
var vr8 = var14[0].F2.F6;
}
s_5 = new long[][]
{
new long[]
{
0
}
};
try
{
var vr10 = (byte)0;
var vr7 = new C0(Vector128.CreateScalar(vr10), 0, new S2(new S0(true, 1, 0, 0), new S0(false, 0, 0, 0), new S0(true, 0, 0, 0)), 1, new S1(Vector128.Create<short>(0), 0, Vector.Create<sbyte>(0), new S0(true, 0, 0, 1), new S0(true, 0, 1, 1), 1), 0, 0);
}
catch (System.Exception)when (var14[0].F0.F4)
{
}
}
catch (System.IndexOutOfRangeException)
{
}
catch (System.ObjectDisposedException)
{
}
catch (System.NotSupportedException)
{
System.Console.WriteLine(var21.F4);
}
catch (System.NotImplementedException)
{
}
catch (System.InvalidOperationException)
{
}
catch (System.ArgumentOutOfRangeException)
{
}
catch (System.ArgumentNullException)
{
var vr0 = s_5[0][0];
S0 var27 = new S0(var0, 0, (sbyte)ArmBase.Arm64.MultiplyHigh(vr0, 0), 0);
}
catch (System.Exception)
{
}
S0 var30 = new S0(false, 0, 0, 0);
var vr4 = (ushort)1;
var vr3 = Vector128.CreateScalar(vr4);
var vr6 = new C0(Vector128.Create(64, 0, 0, 255, 164, 221, 10, 1, 90, 10, 46, 255, 0, 1, 79, 255), s_5[0][0], new S2(new S0(true, -1550151550, -28, 155), new S0(false, -1026795640, -128, 0), new S0(false, -2115060694, 0, 168)), var31[0], new S1(Vector128.Create<short>(-24927), 2757649032644044082UL, Vector.Create<sbyte>(-108), new S0(true, -1166572018, -66, 254), new S0(false, 800442706, -1, 254), 100), var30.F8, (uint)ArmBase.Arm64.LeadingZeroCount(lvar13));
Vector128<ushort> vr15 = vr3;
var vr14 = (byte)0;
var vr13 = new C0(Vector128.CreateScalar(vr14), 0, new S2(new S0(false, 0, 0, 0), new S0(true, 0, 1, 0), new S0(false, 0, 1, 0)), 1, new S1(Vector128.Create<short>(0), 0, Vector.Create<sbyte>(0), new S0(true, 0, 0, 0), new S0(false, 0, 0, 0), 0), 0, 0);
}
}
return s_7;
}
}
Analysis of jitdump
Analysis of jitdump for seed 12698610434432572438
Note: This analysis is AI-generated.
Assertion
Assertion failed '!cache->Lookup(fstruct) || ((*cache)[fstruct].Result == sameSelResult)'
in 'Program:M1():short' during 'VN-based dead store removal'
File: src/coreclr/jit/valuenum.cpp Line: 3675
The assert fires when VNForMapSelectWork is about to memo-ize the result of
select(phiMap, index) and finds the cache already contains a different Result
for the same fstruct key.
Where it happens
The failing method is Program:M1():short. It contains the unrolled
initialization of a S2[] array literal of 10 elements (the var14 = new S2[] { ... }
sequence in the source). Each element of the array consists of three S0 structs,
each of which has many fields (a Vector<ulong>, multiple ulong/uint/bool/byte
fields, a Vector64<ulong>, etc.). After morph, the array initialization becomes
a long chain of STORE_LCL_FLD nodes against a single struct local, which in turn
becomes a long chain of MapPhysicalStore VNs.
During VN-based dead store removal the JIT walks each STORE_LCL_FLD and asks
"does this store get overwritten before being read?". This requires
VNForMapPhysicalSelect on the post-loop memory phi (the unrolled stores are
inside a for loop in the IL), which forwards to VNForMapSelectWork on a phi
map ($3c0 in the dump).
Triggering pattern
Looking at the dump around the failure (lines 490262–490668):
Considering [006033] calls VNForMapPhysicalSelect($3c0, [0:15]) — a select
over a phi.
- The walk iterates the phi's SSA arguments, and for each argument walks the
store chain $2fb → $2fa → … → $2f2 → $3c0 (≈10 store-link nodes), recursing
for each.
- For 9 iterations all phi argument selects return
$200 (sameSelResult stays
$200).
- On the 10th iteration the budget hits 0 (
remaining budget is 0), so
*pBudget <= 0 makes the loop set allSame = false and break out.
- The top-level call returns a fresh function-application VN
$9c1 {$3e5[$142]} rather than the would-be $200.
A bit later, Considering [006037] performs essentially the same walk for
select($2f2, [24:31]). This time the inner walks complete (the chain is
shorter) and produce sameSelResult = $140. When it tries to memo-ize in the
cache at line 3675, the cache already holds an entry for this fstruct —
written previously by a budget-exhausted recursive call that recorded a fresh
function-application VN — and the values do not match, so the assert fails.
The comment immediately above the assert acknowledges this case:
// If we ran out of budget we could have already cached the
// result in the leaf. In that case it is ok to overwrite
// it here.
MapSelectWorkCache* cache = GetMapSelectWorkCache();
assert(!cache->Lookup(fstruct) || ((*cache)[fstruct].Result == sameSelResult));
cache->Set(fstruct, entry, MapSelectWorkCache::Overwrite);
The Overwrite policy on the Set call shows the algorithm intends to
support overwriting an existing entry, but the assert is strictly stronger:
it requires the existing entry's Result to equal the new sameSelResult.
When a budget-exhausted recursive call caches a fallback fresh VN (a brand new
Func2 application created at lines 3691–3699) and a later call with more
remaining budget walks deeper and resolves to a concrete leaf value ($200,
$140, $c0 here), the two Result VNs are necessarily different. That
matches the asserted condition.
Root cause hypothesis
The assert at valuenum.cpp:3675 is too strict for the algorithm as designed.
Two distinct entry points into VNForMapSelectWork for the same fstruct can
legitimately produce different Result VNs when budget exhaustion forces one
of them to bail out to a fresh VNF_MapSelect function application while the
other resolves to the leaf value. The fix is most likely one of:
- Relax the assert to permit the case where the previously-cached entry was
produced under budget exhaustion (e.g. only assert equality when both
entries are "complete"), or
- Avoid caching the fallback fresh VN in budget-exhausted recursive calls so
that later, more-thorough resolutions are free to install the correct entry.
There may also be a correctness concern: if the fallback fresh VN has already
leaked out to callers (and thus into the IR), then later users of the same
fstruct will see a different VN than the one now considered authoritative,
potentially breaking VN-based reasoning downstream. That suggests option (2)
(or some form of explicit "budget-exhausted" marker rather than a real fresh VN)
is more robust.
The repro is a Fuzzlyn-shrunken IL of 1638 bytes consisting almost entirely of
struct-store chains, which is exactly the shape that maximizes the number of
recursive MapPhysicalStore/phi select resolutions per call and so reliably
exhausts the budget.
Attached details.zip file that includes the repro.mc and jitdump.txt files for this example. The repro.mc was collected on 1acc89c.
The following Fuzzlyn-reduced example hits a JIT assertion in
VNForMapSelectWorkduring theVN-based dead store removalphase:Reduced example (Arm64):
Analysis of jitdump
Analysis of jitdump for seed 12698610434432572438
Assertion
The assert fires when
VNForMapSelectWorkis about to memo-ize the result ofselect(phiMap, index)and finds the cache already contains a differentResultfor the same
fstructkey.Where it happens
The failing method is
Program:M1():short. It contains the unrolledinitialization of a
S2[]array literal of 10 elements (thevar14 = new S2[] { ... }sequence in the source). Each element of the array consists of three
S0structs,each of which has many fields (a
Vector<ulong>, multiple ulong/uint/bool/bytefields, a
Vector64<ulong>, etc.). After morph, the array initialization becomesa long chain of
STORE_LCL_FLDnodes against a single struct local, which in turnbecomes a long chain of
MapPhysicalStoreVNs.During
VN-based dead store removalthe JIT walks each STORE_LCL_FLD and asks"does this store get overwritten before being read?". This requires
VNForMapPhysicalSelecton the post-loop memory phi (the unrolled stores areinside a
forloop in the IL), which forwards toVNForMapSelectWorkon a phimap (
$3c0in the dump).Triggering pattern
Looking at the dump around the failure (lines 490262–490668):
Considering [006033]callsVNForMapPhysicalSelect($3c0, [0:15])— a selectover a phi.
store chain
$2fb → $2fa → … → $2f2 → $3c0(≈10 store-link nodes), recursingfor each.
$200(sameSelResultstays$200).remaining budget is 0), so*pBudget <= 0makes the loop setallSame = falseand break out.$9c1 {$3e5[$142]}rather than the would-be$200.A bit later,
Considering [006037]performs essentially the same walk forselect($2f2, [24:31]). This time the inner walks complete (the chain isshorter) and produce
sameSelResult = $140. When it tries to memo-ize in thecache at line 3675, the cache already holds an entry for this
fstruct—written previously by a budget-exhausted recursive call that recorded a fresh
function-application VN — and the values do not match, so the assert fails.
The comment immediately above the assert acknowledges this case:
The
Overwritepolicy on theSetcall shows the algorithm intends tosupport overwriting an existing entry, but the assert is strictly stronger:
it requires the existing entry's
Resultto equal the newsameSelResult.When a budget-exhausted recursive call caches a fallback fresh VN (a brand new
Func2application created at lines 3691–3699) and a later call with moreremaining budget walks deeper and resolves to a concrete leaf value (
$200,$140,$c0here), the twoResultVNs are necessarily different. Thatmatches the asserted condition.
Root cause hypothesis
The assert at valuenum.cpp:3675 is too strict for the algorithm as designed.
Two distinct entry points into
VNForMapSelectWorkfor the samefstructcanlegitimately produce different
ResultVNs when budget exhaustion forces oneof them to bail out to a fresh
VNF_MapSelectfunction application while theother resolves to the leaf value. The fix is most likely one of:
produced under budget exhaustion (e.g. only assert equality when both
entries are "complete"), or
that later, more-thorough resolutions are free to install the correct entry.
There may also be a correctness concern: if the fallback fresh VN has already
leaked out to callers (and thus into the IR), then later users of the same
fstructwill see a different VN than the one now considered authoritative,potentially breaking VN-based reasoning downstream. That suggests option (2)
(or some form of explicit "budget-exhausted" marker rather than a real fresh VN)
is more robust.
The repro is a Fuzzlyn-shrunken IL of 1638 bytes consisting almost entirely of
struct-store chains, which is exactly the shape that maximizes the number of
recursive
MapPhysicalStore/phiselectresolutions per call and so reliablyexhausts the budget.
Attached details.zip file that includes the repro.mc and jitdump.txt files for this example. The repro.mc was collected on 1acc89c.
cc @dotnet/jit-contrib
details.zip