Repository navigation
[wasm] R2R: 32-byte struct passed by value is corrupted (fields shifted by one word) #131639
Description
Activity
- addedarea-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMICLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
on Jul 31, 2026 dotnet-policy-service commented
on Jul 31, 2026 ContributorMore actionsTagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.AI's take on this:
Wasm thunks are keyed globally by signature string, and structs are encoded as
S<N>— size only, no alignment. WasmLowering.RaiseSignature recovers the parameter type via GetCachedStructOfSize(N) , which returns whatever struct of that size the compilation happened to see first.But the wasm ArgIterator aligns a struct's transition-block slot to clamp(structAlignment, 8, 16) . So a 32-byte struct of long s (align 8) and one of Int128 s (align 16) have different argument layouts yet share one thunk.
CoreLib's cached size-32 stand-in is ValueTuple<Int128,Int128> → align 16. Its viS32ip thunk therefore places the struct at TB+24 / y at TB+56, while the real (align-8) layout is TB+16 / TB+48. Every arg after the first shifts by 8 — exactly the reported corruption.
Explains everything: only size 32; only when a preceding arg leaves the offset at 8 (not 16-aligned); only the first call (later calls go direct-R2R, bypassing the interp→R2R thunk); and why (long,S32,int) / (int,int,S32,int) pass (no CoreLib thunk for those strings, so the app's own correct thunk is used).
I can put up a plausible fix, but wonder if this intersects with #131492. Let me dig in a bit more.
yeah this should build on top of #131492
I can't reproduce this anymore
I have repro in blazor onClick event
ThrowRtlRestoreContextTag() (helpers.cpp:562) ::RtlRestoreContext(PCONTEXT, PEXCEPTION_RECORD) (helpers.cpp:591) ClrRestoreNonvolatileContext(_CONTEXT*, unsigned long) (threads.cpp:6571) EECodeManager::ResumeAfterCatch(_CONTEXT*, unsigned long, bool) (eetwain.cpp:1826) InterlockedDecrement (exceptionhandling.cpp:3383) Thread::DecPreventAbort() (threads.h:2230) CallCatchFunclet(unsigned char*, REGDISPLAY*, ExInfo*) (exceptionhandling.cpp:3270) CrawlFrame::GetRegisterSet() (exceptionhandling.cpp:4560) DispatchExSecondPass(ExInfo*) (exceptionhandling.cpp:4520) DispatchManagedException(Object*, _CONTEXT*, _EXCEPTION_RECORD*, ExKind) (exceptionhandling.cpp:1816) IL_Throw_Impl(Object*, TransitionBlock*) (jithelpers.cpp:815) IL_Throw_IMPL(unsigned long, Object*, unsigned long) (jithelpers.cpp:902) ::IL_Throw() (jithelpers.cpp:894) $Microsoft_JSInterop_Microsoft_JSInterop_JSRuntime__GetObjectReference (Microsoft.JSInterop.yn49ktdxft.wasm:0x8e09) $Microsoft_JSInterop_Microsoft_JSInterop_Infrastructure_DotNetDispatcher__BeginInvokeDotNet (Microsoft.JSInterop.yn49ktdxft.wasm:0xa6ad) $Microsoft_AspNetCore_Components_WebAssembly_Microsoft_AspNetCore_Components_WebAssembly_Services_DefaultWebAssemblyJSRuntime___c___BeginInvokeDotNet_b__12_0 (Microsoft.AspNetCore.Components.WebAssembly.f751340po3.wasm:0x2ab66) $WasmInterpreterToR2RThunk_vTS32p_ (System.Private.CoreLib.5kzzcpu1rm.wasm:0x743285) InvokeCalliStub(unsigned long, void (*)(unsigned long, signed char*, signed char*), signed char*, signed char*, Object**) (helpers.cpp:1870) InterpByteCodeStart::GetByteCodes() const (interpexec.cpp:3557) InterpExecMethod(InterpreterFrame*, InterpMethodContextFrame*, InterpThreadContext*, ExceptionClauseArgs*) (interpexec.cpp:1433) ::ExecuteInterpretedMethod(TransitionBlock *, TADDR, void *) (prestub.cpp:2100) ExecuteInterpretedMethodWithArgs_PortableEntryPoint(unsigned long, TransitionBlock*, unsigned long, signed char*) (prestub.cpp:2288) $WasmR2RToInterpreterThunk_viS32ip_ (Microsoft.AspNetCore.Components.WebAssembly.f751340po3.wasm:0x5cdc2) $Microsoft_AspNetCore_Components_WebAssembly_Microsoft_AspNetCore_Components_WebAssembly_Services_DefaultWebAssemblyJSRuntime__BeginInvokeDotNet (Microsoft.AspNetCore.Components.WebAssembly.f751340po3.wasm:0x291e0) $Microsoft_AspNetCore_Components_WebAssembly_Microsoft_AspNetCore_Components_WebAssembly_Services_DefaultWebAssemblyJSRuntime_____Wrapper_BeginInvokeDotNet_1855805417_g____Stub_23_0 (Microsoft.AspNetCore.Components.WebAssembly.f751340po3.wasm:0x2a6bb) $Microsoft_AspNetCore_Components_WebAssembly_Microsoft_AspNetCore_Components_WebAssembly_Services_DefaultWebAssemblyJSRuntime____Wrapper_BeginInvokeDotNet_1855805417 (Microsoft.AspNetCore.Components.WebAssembly.f751340po3.wasm:0x2a083) (anonymous namespace)::CallFunc_I32_RetVoid_PE(unsigned long, signed char*, signed char*) (callhelpers-interp-to-managed.cpp:505) InvokeCalliStub(unsigned long, void (*)(unsigned long, signed char*, signed char*), signed char*, signed char*, Object**) (helpers.cpp:1870) InterpByteCodeStart::GetByteCodes() const (interpexec.cpp:3557) InterpExecMethod(InterpreterFrame*, InterpMethodContextFrame*, InterpThreadContext*, ExceptionClauseArgs*) (interpexec.cpp:1433) ::ExecuteInterpretedMethod(TransitionBlock *, TADDR, void *) (prestub.cpp:2100) ExecuteInterpretedMethodWithArgs_PortableEntryPoint(unsigned long, TransitionBlock*, unsigned long, signed char*) (prestub.cpp:2288) $WasmR2RToInterpreterThunk_iTiS8p_ (System.Private.CoreLib.5kzzcpu1rm.wasm:0x731cc8) $S_P_CoreLib_System_Reflection_MethodBaseInvoker__InvokeWithOneArg (System.Private.CoreLib.5kzzcpu1rm.wasm:0x385d73) $S_P_CoreLib_System_Reflection_RuntimeMethodInfo__Invoke (System.Private.CoreLib.5kzzcpu1rm.wasm:0x3a6ccc) $S_P_CoreLib_System_Reflection_MethodBase__Invoke (System.Private.CoreLib.5kzzcpu1rm.wasm:0x38428c) $System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JSHostImplementation___c__DisplayClass17_0___BindManagedFunction_b__0 (System.Runtime.InteropServices.JavaScript.vmpbo5azdi.wasm:0xa20f) $WasmInterpreterToR2RThunk_vTip_ (System.Private.CoreLib.5kzzcpu1rm.wasm:0x7443eb) InvokeCalliStub(unsigned long, void (*)(unsigned long, signed char*, signed char*), signed char*, signed char*, Object**) (helpers.cpp:1870) InterpByteCodeStart::GetByteCodes() const (interpexec.cpp:3557) InterpExecMethod(InterpreterFrame*, InterpMethodContextFrame*, InterpThreadContext*, ExceptionClauseArgs*) (interpexec.cpp:1433) ::ExecuteInterpretedMethod(TransitionBlock *, TADDR, void *) (prestub.cpp:2100) ExecuteInterpretedMethodWithArgs(unsigned long, signed char*, unsigned long, void*, unsigned long) (prestub.cpp:2164) ::ExecuteInterpretedMethodFromUnmanaged(MethodDesc *, int8_t *, size_t, int8_t *, PCODE) (prestub.cpp:2313) Call_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_CallJSExport_I32_I32_RetVoid(int, void*) (callhelpers-reverse.cpp:118) ::SystemInteropJS_CallJSExport(int32_t, void *) (callhelpers-reverse.cpp:123) SystemInteropJS_CallJSExport (dotnet.native.a4ygtxctyj.js:3340) ie (dotnet.runtime.6dqfcdbqmc.js:4) (anonymous) (dotnet.runtime.6dqfcdbqmc.js:4) beginInvokeDotNetFromJS (blazor.webassembly.js:1) invokeDotNetMethodAsync (blazor.webassembly.js:1) invokeMethodAsync (blazor.webassembly.js:1) (anonymous) (blazor.webassembly.js:1) T (blazor.webassembly.js:1) (anonymous) (blazor.webassembly.js:1) invokeWhenHeapUnlocked (blazor.webassembly.js:1) (anonymous) (blazor.webassembly.js:1) T (blazor.webassembly.js:1) R (blazor.webassembly.js:1) dispatchGlobalEventToAllElements (blazor.webassembly.js:1) onGlobalEvent (blazor.webassembly.js:1)
Description
On
browser-wasm(CoreCLR) withPublishReadyToRun, a 32-byte struct (four 8-byte fields) passed by value to a method is corrupted: the callee reads it shifted up by one 8-byte word, so each field takes the value of the next and the following argument bleeds in. 16- and 24-byte structs pass correctly.Repro
E32must be ReadyToRun-compiled. Interpreted execution is correct.Expected
1|31,32,33,34|2Actual
1|32,33,34,2|0s.Areads the inputB,s.BreadsC,s.CreadsD,s.Dreads the trailingint y(2), andyreads garbage (0) — i.e. the struct argument is read one 8-byte word too high.Notes
{ long, long }) and 24-byte ({ long, long, long }) structs pass correctly; only the 32-byte (four-word) struct is affected.Configuration
-os browser -a wasm, CoreCLR,PublishReadyToRun(crossgen2,--obj-format:wasm)Note
This issue was written with the assistance of GitHub Copilot.