Skip to content

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

Closed

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.

Activity

  1. added
    arch-wasmWebAssembly architecture
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Jul 24, 2026
  2. dotnet-policy-service commented on Jul 24, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
    See info in area-owners.md if you want to be subscribed.

  3. dotnet-policy-service commented on Jul 24, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
    See info in area-owners.md if you want to be subscribed.

  4. lewing commented on Jul 24, 2026

    @lewing
    MemberAuthor

    Debugger root-cause (nesm WASI): R2R↔interpreter boundary for a many-Vector128 signature

    Reproduced under the nesm WASI interpreter (full R2R managed-name symbolication) on both browser and wasi Release. The NRE originates in the R2R generic method Base64Helper.DecodeFrom<Base64UrlDecoderByte,byte> at the call that dispatches the vectorized inner helper, which has wasm signature iTVVVVVVVVip — i.e. a generic-context/this (T) + 8× Vector128 (V) + i32 + PCODE.

    Key facts from the debugger:

    • That inner call always crosses the R2R→interpreter boundary via WasmDelayLoadHelper → WasmR2RToInterpreterThunk(iTVVVVVVVVip) → interp, independent of JitWasmSimdNyiToR2RUnsupported (0 or 1). The NRE is thrown inside the interpreted execution of that large-signature helper.
    • The immediately-preceding Base64UrlDecoderByte.TryLoadVector128 is not separately reached, so the fault is at the 8×Vector128 helper dispatch, not the LUT loads.
    • The bug threshold (source ≥ 64 bytes = first full Vector128 iteration) lines up exactly with taking this vectorized path.

    This points at the argument marshalling for a signature carrying many Vector128 args plus a generic context across the R2R↔interp boundary — the same 'V' (v128) thunk-signature family recently introduced (#131246, #131239, #131227). The sig-key classification (helpers.cpp ToV128→'V') and the crossgen type-lowering (WasmLowering.IsWasmV128Type/LowerToAbiType) both look correct; the remaining gap is in arg layout/size on the boundary (candidates: ArgIterator.SizeOfFrameArgumentArray/per-arg offsets for v128 on wasm vs the interpreter's arg-local layout; the generic-context slot offset when preceded/followed by 8× v128). A latent related gap: src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs has no 'V' case, so TokenToSlotCount('V') returns 1 slot (8B) instead of 2 (16B) — not on this exact crossgen path, but worth closing.

    Minimal repro (unchanged) — Base64Url.DecodeFromUtf8 with ≥64 valid base64url bytes into a smaller destination: correct under interp, NRE under R2R.

    Note

    This analysis was produced with the assistance of GitHub Copilot.

  5. added this to the 11.0.0 milestone on Jul 24, 2026
  6. removed
    untriagedNew issue has not been triaged by the area owner
    on Jul 24, 2026
  7. JulieLeeMSFT commented on Jul 24, 2026

    @JulieLeeMSFT
    Member
  8. added a commit that references this issue on Jul 24, 2026
    41df12a
  9. added a commit that references this issue on Jul 24, 2026
    174ad35
  10. added a commit that references this issue on Jul 26, 2026
    25ea69f
  11. locked and limited conversation to collaborators on Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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