From be366f02d4486b38876274f6e4bd2e0eba90fc63 Mon Sep 17 00:00:00 2001 From: Adam Perlin Date: Mon, 20 Jul 2026 15:45:31 -0700 Subject: [PATCH 1/4] Properly consume multiply used addr from null check in Wasm regalloc --- src/coreclr/jit/regallocwasm.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index 2599d53996432c..f15f6540a3f96d 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -728,6 +728,12 @@ void WasmRegAlloc::CollectReferencesForHardwareIntrinsic(GenTreeHWIntrinsic* nod return; } + GenTree* addr; + if (node->OperIsMemoryLoad(&addr) || node->OperIsMemoryStore(&addr)) + { + ConsumeTemporaryRegForOperand(addr DEBUGARG("hardware intrinsic memory address null check")); + } + // Only intrinsics with an immediate operand can need the jump-table fallback. if (!HWIntrinsicInfo::HasImmediateOperand(node->GetHWIntrinsicId())) { From 2e314045bdc5dbac4335cbcbeab34d66f5510fb2 Mon Sep 17 00:00:00 2001 From: Adam Perlin Date: Mon, 20 Jul 2026 15:46:10 -0700 Subject: [PATCH 2/4] jit-format --- src/coreclr/jit/rangecheck.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/rangecheck.h b/src/coreclr/jit/rangecheck.h index 4880f7696fb070..74c4df51e2ea1c 100644 --- a/src/coreclr/jit/rangecheck.h +++ b/src/coreclr/jit/rangecheck.h @@ -328,10 +328,10 @@ struct RangeOps { if (unsignedAdd) { - bool r1StraddlesZero = r1.IsConstantRange() && (r1.LowerLimit().GetConstant() < 0) && - (r1.UpperLimit().GetConstant() >= 0); - bool r2StraddlesZero = r2.IsConstantRange() && (r2.LowerLimit().GetConstant() < 0) && - (r2.UpperLimit().GetConstant() >= 0); + bool r1StraddlesZero = + r1.IsConstantRange() && (r1.LowerLimit().GetConstant() < 0) && (r1.UpperLimit().GetConstant() >= 0); + bool r2StraddlesZero = + r2.IsConstantRange() && (r2.LowerLimit().GetConstant() < 0) && (r2.UpperLimit().GetConstant() >= 0); if (r1StraddlesZero || r2StraddlesZero) { // Signed intervals that straddle zero are not monotonic when interpreted as unsigned. @@ -355,7 +355,8 @@ struct RangeOps static_assert(CheckedOps::Unsigned == true); // For unsigned adds, require both unsigned and signed endpoint sums to not overflow. bool requestedAddOverflows = CheckedOps::AddOverflows(a.GetConstant(), b.GetConstant(), unsignedAdd); - bool signedEndpointOverflows = unsignedAdd && CheckedOps::AddOverflows(a.GetConstant(), b.GetConstant(), CheckedOps::Signed); + bool signedEndpointOverflows = + unsignedAdd && CheckedOps::AddOverflows(a.GetConstant(), b.GetConstant(), CheckedOps::Signed); if (!requestedAddOverflows && !signedEndpointOverflows) { if (a.IsConstant() && b.IsConstant()) From 3680435a6bfb210a77fb8cd913a0838d9c7c9385 Mon Sep 17 00:00:00 2001 From: Adam Perlin Date: Tue, 21 Jul 2026 14:35:05 -0700 Subject: [PATCH 3/4] Wasm Regalloc: Properly consume multiply used address from null check for jump table and non jump table hwi cases --- src/coreclr/jit/regallocwasm.cpp | 51 ++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index f15f6540a3f96d..e019444ba8788f 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -715,8 +715,10 @@ void WasmRegAlloc::CollectReferencesForLclVar(GenTreeLclVar* lclVar) // node - The GT_HWINTRINSIC node // // Notes: -// This is a no-op unless a hw intrinsic needs a jump table fallback, in which case we have to consume -// temporary registers for its operands. +// There are only 3 cases where we need to consume temporary registers for a hardware intrinsic: +// 1) A swizzle with a contained mask (source operand multiply used) +// 2) A hardware intrinsic with a non-constant immediate operand that requires a jump table fallback (all operands multiply used) +// 3) A memory load/store hardware intrinsic that requires a null check of the address (address multiply used for null check) void WasmRegAlloc::CollectReferencesForHardwareIntrinsic(GenTreeHWIntrinsic* node) { // A constant, in-range mask Swizzle is lowered to an immediate i8x16.shuffle, which reuses the @@ -728,33 +730,36 @@ void WasmRegAlloc::CollectReferencesForHardwareIntrinsic(GenTreeHWIntrinsic* nod return; } - GenTree* addr; - if (node->OperIsMemoryLoad(&addr) || node->OperIsMemoryStore(&addr)) + bool needsJumpTableFallback = false; + if (HWIntrinsicInfo::HasImmediateOperand(node->GetHWIntrinsicId())) { - ConsumeTemporaryRegForOperand(addr DEBUGARG("hardware intrinsic memory address null check")); - } - - // Only intrinsics with an immediate operand can need the jump-table fallback. - if (!HWIntrinsicInfo::HasImmediateOperand(node->GetHWIntrinsicId())) - { - return; + GenTree* immOp = node->GetImmOp(); + // Only intrinsics that have a non-constant immediate need a jump-table fallback, and mark operands + // MultiplyUsed during Lowering (see Lowering::LowerHWIntrinsic in lowerwasm.cpp). + if (!immOp->IsCnsIntOrI()) + { + needsJumpTableFallback = true; + } } - GenTree* immOp = node->GetImmOp(); - - // Only intrinsics that have a non-constant immediate need a jump-table fallback, and mark operands - // MultiplyUsed during Lowering (see Lowering::LowerHWIntrinsic in lowerwasm.cpp). - if (immOp->IsCnsIntOrI()) + if (needsJumpTableFallback) { - return; + // All operands are marked multiply used in this case, so we consume a temporary register for each operand + // in reverse (wasm stack) order. + int operandCount = static_cast(node->GetOperandCount()); + for (int i = operandCount; i >= 1; i--) + { + ConsumeTemporaryRegForOperand(node->Op(i) DEBUGARG("hardware intrinsic fallback")); + } } - - // All operands are marked multiply used, so we consume a temporary register for each operand - // in reverse (wasm stack) order. - int operandCount = static_cast(node->GetOperandCount()); - for (int i = operandCount; i >= 1; i--) + else { - ConsumeTemporaryRegForOperand(node->Op(i) DEBUGARG("hardware intrinsic fallback")); + // We still need to consume a temporary register due to a null check of the address operand for memory load/store intrinsics. + GenTree* addr; + if (node->OperIsMemoryLoad(&addr) || node->OperIsMemoryStore(&addr)) + { + ConsumeTemporaryRegForOperand(addr DEBUGARG("hardware intrinsic memory address null check")); + } } } From 5db48d8dc7b79f5d4105dbdc6476be9d699335e2 Mon Sep 17 00:00:00 2001 From: Adam Perlin Date: Tue, 21 Jul 2026 14:38:37 -0700 Subject: [PATCH 4/4] jit-format --- src/coreclr/jit/regallocwasm.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index e019444ba8788f..fe03c325210047 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -717,8 +717,10 @@ void WasmRegAlloc::CollectReferencesForLclVar(GenTreeLclVar* lclVar) // Notes: // There are only 3 cases where we need to consume temporary registers for a hardware intrinsic: // 1) A swizzle with a contained mask (source operand multiply used) -// 2) A hardware intrinsic with a non-constant immediate operand that requires a jump table fallback (all operands multiply used) -// 3) A memory load/store hardware intrinsic that requires a null check of the address (address multiply used for null check) +// 2) A hardware intrinsic with a non-constant immediate operand that requires a jump table fallback (all operands +// multiply used) +// 3) A memory load/store hardware intrinsic that requires a null check of the address (address +// multiply used for null check) void WasmRegAlloc::CollectReferencesForHardwareIntrinsic(GenTreeHWIntrinsic* node) { // A constant, in-range mask Swizzle is lowered to an immediate i8x16.shuffle, which reuses the @@ -754,7 +756,8 @@ void WasmRegAlloc::CollectReferencesForHardwareIntrinsic(GenTreeHWIntrinsic* nod } else { - // We still need to consume a temporary register due to a null check of the address operand for memory load/store intrinsics. + // We still need to consume a temporary register due to a null check of the address operand for memory + // load/store intrinsics. GenTree* addr; if (node->OperIsMemoryLoad(&addr) || node->OperIsMemoryStore(&addr)) {