From 525c7caaf25e8c8b3bb5ec0b3b249bd82bf3e12f Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Wed, 30 Sep 2026 15:35:36 -0500 Subject: [PATCH 1/2] Fix null ReturnTypeDesc dereference in SpillStructCallResult on wasm Wasm has FEATURE_MULTIREG_RET == 0, so GenTreeCall::GetReturnTypeDesc() returns nullptr. Spilling a struct call result that is returned in a single register but whose destination local has no primitive register type (e.g. a struct wrapping Vector128, returned as v128) dereferenced it and crashed crossgen2. Fixes #134976 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/jit/lower.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 4879f8a0a885df..876a11063267e9 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -6271,8 +6271,13 @@ GenTreeLclVar* Lowering::SpillStructCallResult(GenTreeCall* call) const m_compiler->lvaSetVarDoNotEnregister(spillNum DEBUGARG(DoNotEnregisterReason::LocalField)); CORINFO_CLASS_HANDLE retClsHnd = call->gtRetClsHnd; m_compiler->lvaSetStruct(spillNum, retClsHnd, false); - unsigned offset = call->GetReturnTypeDesc()->GetSingleReturnFieldOffset(); - GenTreeLclFld* spill = m_compiler->gtNewStoreLclFldNode(spillNum, call->TypeGet(), offset, call); +#if FEATURE_MULTIREG_RET + unsigned offset = call->GetReturnTypeDesc()->GetSingleReturnFieldOffset(); +#else + // Without multi-reg return support there is no return type descriptor and the value is at offset 0. + unsigned offset = 0; +#endif + GenTreeLclFld* spill = m_compiler->gtNewStoreLclFldNode(spillNum, call->TypeGet(), offset, call); BlockRange().InsertAfter(call, spill); ContainCheckStoreLoc(spill); From 32e912b2876c4adcd209369375407b36bf5f0716 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Wed, 30 Sep 2026 16:07:17 -0500 Subject: [PATCH 2/2] Update SpillStructCallResult comments The function is no longer only reached for 3/5/6/7-byte returns; on Wasm it also handles single-register struct returns (e.g. v128) whose layout has no primitive register type. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/jit/lower.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 876a11063267e9..5dd38af4cd04cf 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -6259,7 +6259,7 @@ void Lowering::LowerStoreSingleRegCallStruct(GenTreeBlk* store) // SpillStructCallResult: Spill call result to memory. // // Arguments: -// call - call with 3, 5, 6 or 7 return size that has to be spilled to memory. +// call - call returning a struct in a single register whose layout has no primitive register type. // // Return Value: // load of the spilled variable. @@ -6267,14 +6267,15 @@ void Lowering::LowerStoreSingleRegCallStruct(GenTreeBlk* store) GenTreeLclVar* Lowering::SpillStructCallResult(GenTreeCall* call) const { // TODO-1stClassStructs: we can support this in codegen for `GT_STORE_BLK` without new temps. - const unsigned spillNum = m_compiler->lvaGrabTemp(true DEBUGARG("Return value temp for an odd struct return size")); + const unsigned spillNum = + m_compiler->lvaGrabTemp(true DEBUGARG("Return value temp for a non-enregisterable struct return")); m_compiler->lvaSetVarDoNotEnregister(spillNum DEBUGARG(DoNotEnregisterReason::LocalField)); CORINFO_CLASS_HANDLE retClsHnd = call->gtRetClsHnd; m_compiler->lvaSetStruct(spillNum, retClsHnd, false); #if FEATURE_MULTIREG_RET unsigned offset = call->GetReturnTypeDesc()->GetSingleReturnFieldOffset(); #else - // Without multi-reg return support there is no return type descriptor and the value is at offset 0. + // No ReturnTypeDesc without FEATURE_MULTIREG_RET. unsigned offset = 0; #endif GenTreeLclFld* spill = m_compiler->gtNewStoreLclFldNode(spillNum, call->TypeGet(), offset, call);