unlock unlimited code runs for $79, limited time →
NOSSLETTER.techLIVE
back to OSSpedia
dotnet/runtime
senior7/10
compiler-internalserror-handlingtesting-strategy

Fix SIMD primitive zero initialization

Project: dotnet/runtime | Language: C# | Concepts: compiler-internals, error-handling, testing-strategy | Difficulty: senior
Link: https://github.com/dotnet/runtime/pull/133100

The problem

When the JIT compiler tried to optimize certain memory zeroing patterns for SIMD types (like Vector128 or Vector256), it created a SIMD zero node but failed to update the store instruction to use this new node as its data source. As a result, the code generator would emit a store of an integer zero into a SIMD variable, which is invalid and could cause runtime failures or incorrect code generation.

Why it's non-trivial

A naive fix might just create the correct zero node, but unless the store's data source is also updated to reference this new node, the backend will still emit the wrong instructions. The issue is subtle because it only manifests for specific code patterns and compilation modes (e.g., Tier0 JIT), and the incorrect IR tree looks superficially valid but produces incorrect machine code.

The approach

The core fix is in MorphInitBlockHelper::TryPrimitiveInit(), where the SIMD zero node is created and must be properly wired into the store:

if (varTypeIsSIMD(lclVarType))
{
    m_src = m_compiler->gtNewZeroConNode(lclVarType);
+   m_src->SetMorphed(m_compiler);
+   m_store->Data() = m_src;
}
  • m_src = m_compiler->gtNewZeroConNode(lclVarType); creates a new zero constant node for the correct SIMD type.
  • m_src->SetMorphed(m_compiler); ensures the new node is marked as "morphed", meaning it's ready for the next compilation phase.
  • m_store->Data() = m_src; is the critical fix: it updates the store's data operand to use the new SIMD zero node, not the original integer zero.

A new regression test was added to verify the fix across both Vector128 and Vector256, and in both FullOpts and Tier0 JIT modes:

[Fact]
public static void TestEntryPoint()
{
    Assert.Equal(Vector128<ulong>.Zero, ZeroVector128FullOpts());
    Assert.Equal(Vector256<ulong>.Zero, ZeroVector256FullOpts());
    Assert.Equal(Vector128<ulong>.Zero, ZeroVector128Tier0());
    Assert.Equal(Vector256<ulong>.Zero, ZeroVector256Tier0());
}
  • This test ensures that the zeroing works correctly for both vector sizes and both optimization levels, catching any regressions in codegen.

The corresponding .csproj includes a test environment variable to enable tiered compilation:

<CLRTestEnvironmentVariable Include="DOTNET_TieredCompilation" Value="1" />
  • This guarantees the test covers the Tier0 JIT path, which was previously broken.

From the review thread

No notable review discussion on this PR.

Takeaways

  • When transforming compiler IR, always ensure that newly created nodes are properly wired into all relevant parent nodes—missing a single reference can produce subtle, hard-to-diagnose bugs.
  • Regression tests should cover all relevant code paths and modes (e.g., different optimization levels, JIT tiers) to catch issues that only appear under specific conditions.
  • Fixes to compiler internals often require both an IR change and a test that exercises the exact codegen scenario that was previously broken.