Skip to content

[wasm][R2R] Base64Url.DecodeFromUtf8 throws spurious NullReferenceException on the Vector128 decode path (Release) #131299

Description

@lewing

Summary

Under ReadyToRun on WebAssembly (Release / shipping config), System.Buffers.Text.Base64Url.DecodeFromUtf8 throws a spurious NullReferenceException for inputs large enough to take the Vector128 (SIMD) decode path. The same call is correct under the interpreter (DOTNET_ReadyToRun=0). Reproduces on both browser and wasi with Release crossgen2 + Release corerun, so it is an OS-neutral R2R codegen bug.

This is distinct from #131298 (that one is Debug/checked-only). This one reproduces in the Release/shipping configuration.

Minimal repro

using System;
using System.Buffers.Text;
class Program {
    static int Main() {
        string alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
        Span<byte> source = new byte[64];
        for (int i = 0; i < 64; i++) source[i] = (byte)alpha[i % alpha.Length];
        Span<byte> dst = new byte[32];
        var st = Base64Url.DecodeFromUtf8(source, dst, out int c, out int w, isFinalBlock: true);
        Console.WriteLine($"status={st} c={c} w={w}");
        return 42;
    }
}
  • R2R off (interp): status=DestinationTooSmall c=... w=... (correct)
  • R2R on:
    Unhandled exception. System.NullReferenceException
       at System.Buffers.Text.Base64Helper.DecodeFrom[TBase64Decoder,T](TBase64Decoder decoder, ReadOnlySpan`1 source, Span`1 bytes, Int32& bytesConsumed, Int32& bytesWritten, Boolean isFinalBlock, Boolean ignoreWhiteSpace)
       at System.Buffers.Text.Base64Url.DecodeFromUtf8(...)
    

Trigger threshold

source length path R2R result
≤ 32 bytes scalar correct (DestinationTooSmall)
≥ 64 bytes Vector128Decode NullReferenceException

Independent of isFinalBlock (both true/false fail at ≥64). Reproduces with both --codegenopt:JitWasmSimdNyiToR2RUnsupported=0 and =1, so it is not simply "SIMD allowed in R2R".

Localization

  • Faulting method: Base64Helper.DecodeFrom<TBase64Decoder,T> → the wasm vectorized branch Vector128Decode<TBase64Decoder,T> (guarded by PackedSimd.IsSupported, src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs).
  • The scalar path uses decoder.DecodingMap (a ReadOnlySpan<sbyte> → RVA data blob) and works. The vectorized path additionally loads decoder.Vector128LutHigh/Low/Shift (ReadOnlySpan<int>/<uint> LUTs) and calls generic struct-interface methods decoder.TryLoadVector128 / decoder.TryDecode128Core.
  • Isolated micro-repros that do not reproduce (so the bug is more specific than any of these alone): a plain ReadOnlySpan<int> collection-expression + Vector128.Create; and a generic struct span-property dispatch + Vector128.Create. The NRE only appears in the full Vector128Decode context, pointing at the R2R codegen of that specialized generic method (Vector128Decode<TBase64Decoder,T>) rather than any single lowered construct.

Impact

  • Affects shipping (Release) R2R-on-wasm: any Base64Url (and likely Base64) decode ≥ ~64 bytes throws. Surfaced by Microsoft.Bcl.Memory.Tests (Base64UrlDecoderUnitTests.*) — 12 tests fail with this NRE under R2R, all pass under interp.

Environment

  • CoreCLR R2R-on-wasm, Release crossgen2 + Release corerun, browser and wasi, wasm32.

Note

This issue was authored with the assistance of GitHub Copilot.

Metadata

Metadata

Assignees

Labels

arch-wasmWebAssembly architecturearea-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions