From 3ed71429f11ab86941e134e6be684af7e13be0e9 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Fri, 3 Apr 2026 10:41:57 -0700 Subject: [PATCH 01/11] Fix assert while compiling BitConverter.ToBFloat16 --- src/coreclr/jit/codegenwasm.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 3c3ebe4d9e154f..2b324c577eb9c6 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2247,9 +2247,18 @@ void CodeGen::genCodeForStoreLclVar(GenTreeLclVar* tree) // enregistered locals need to be handled. LclVarDsc* varDsc = m_compiler->lvaGetDesc(tree); regNumber targetReg = tree->GetRegNum(); + var_types type = tree->TypeGet(); assert(genIsValidReg(targetReg) && varDsc->lvIsRegCandidate()); - GetEmitter()->emitIns_I(INS_local_set, emitTypeSize(tree), WasmRegToIndex(targetReg)); + if ( + (varDsc->lvType == TYP_STRUCT) && + (type == TYP_STRUCT) + ) + { + type = varDsc->GetLayout()->GetRegisterType(); + } + + GetEmitter()->emitIns_I(INS_local_set, emitTypeSize(type), WasmRegToIndex(targetReg)); genUpdateLifeStore(tree, targetReg, varDsc); } From b2cbe15b13af11497c29e63126235359e466cfac Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Fri, 3 Apr 2026 13:08:11 -0700 Subject: [PATCH 02/11] Fix for assert in codegen for System.Numerics.Vector`1[System.__Canon]:op_Multiply caused by dead store removal --- src/coreclr/jit/codegenwasm.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 2b324c577eb9c6..e4a303666acd28 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2210,6 +2210,13 @@ void CodeGen::genCodeForLclVar(GenTreeLclVar* tree) if (!varDsc->lvIsRegCandidate()) { var_types type = varDsc->GetRegisterType(tree); + // Block stores can get pruned from LIR, and when that happens a LclVar node of TYP_STRUCT can get left behind + // in LIR. Its RegisterType will be TYP_UNDEF, but it will be flagged with IsUnusedValue to tell us to ignore it. + if (type == TYP_UNDEF) + { + assert(tree->IsUnusedValue()); + return; + } GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetFramePointerRegIndex()); GetEmitter()->emitIns_S(ins_Load(type), emitTypeSize(type), tree->GetLclNum(), 0); WasmProduceReg(tree); From ef62c3de1ab76c66ccedf860a04cbbe0b6ce6ef0 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Fri, 3 Apr 2026 13:13:01 -0700 Subject: [PATCH 03/11] jit-format --- src/coreclr/jit/codegenwasm.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index e4a303666acd28..66585de4282018 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2211,7 +2211,8 @@ void CodeGen::genCodeForLclVar(GenTreeLclVar* tree) { var_types type = varDsc->GetRegisterType(tree); // Block stores can get pruned from LIR, and when that happens a LclVar node of TYP_STRUCT can get left behind - // in LIR. Its RegisterType will be TYP_UNDEF, but it will be flagged with IsUnusedValue to tell us to ignore it. + // in LIR. Its RegisterType will be TYP_UNDEF, but it will be flagged with IsUnusedValue to tell us to ignore + // it. if (type == TYP_UNDEF) { assert(tree->IsUnusedValue()); @@ -2257,10 +2258,7 @@ void CodeGen::genCodeForStoreLclVar(GenTreeLclVar* tree) var_types type = tree->TypeGet(); assert(genIsValidReg(targetReg) && varDsc->lvIsRegCandidate()); - if ( - (varDsc->lvType == TYP_STRUCT) && - (type == TYP_STRUCT) - ) + if ((varDsc->lvType == TYP_STRUCT) && (type == TYP_STRUCT)) { type = varDsc->GetLayout()->GetRegisterType(); } From 3b029920aae8a4da0fd507423bf85c6102407ab2 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Fri, 3 Apr 2026 15:15:54 -0700 Subject: [PATCH 04/11] Address PR feedback --- src/coreclr/jit/codegenwasm.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 66585de4282018..14bedae2e61534 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2255,14 +2255,9 @@ void CodeGen::genCodeForStoreLclVar(GenTreeLclVar* tree) // enregistered locals need to be handled. LclVarDsc* varDsc = m_compiler->lvaGetDesc(tree); regNumber targetReg = tree->GetRegNum(); - var_types type = tree->TypeGet(); + var_types type = varDsc->GetRegisterType(tree); assert(genIsValidReg(targetReg) && varDsc->lvIsRegCandidate()); - if ((varDsc->lvType == TYP_STRUCT) && (type == TYP_STRUCT)) - { - type = varDsc->GetLayout()->GetRegisterType(); - } - GetEmitter()->emitIns_I(INS_local_set, emitTypeSize(type), WasmRegToIndex(targetReg)); genUpdateLifeStore(tree, targetReg, varDsc); } From 7e192a28805164cd8edfe9a73f40845d5d68bb44 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Wed, 8 Apr 2026 16:07:31 -0700 Subject: [PATCH 05/11] Remove the dead lcl_fld/lcl_var --- src/coreclr/jit/liveness.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index 01e58a6eab3ea3..23a1f680a7a513 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -2331,6 +2331,7 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR { noway_assert(VarSetOps::IsSubset(m_compiler, keepAliveVars, life)); + GenTree* mostRecentLocalVarOrField = nullptr; LIR::Range& blockRange = LIR::AsRange(block); GenTree* firstNode = blockRange.FirstNode(); if (firstNode == nullptr) @@ -2394,6 +2395,7 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR { GenTreeLclVarCommon* const lclVarNode = node->AsLclVarCommon(); LclVarDsc& varDsc = m_compiler->lvaTable[lclVarNode->GetLclNum()]; + mostRecentLocalVarOrField = node; if (TLiveness::EliminateDeadCode && node->IsUnusedValue()) { @@ -2463,6 +2465,18 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR { Lowering::TransformUnusedIndirection(data->AsIndir(), m_compiler, block); } + else if (data == mostRecentLocalVarOrField) + { + // The unused lcl_var or lcl_field on the rhs of a removed block store may be a struct + // which cannot always be loaded onto the Wasm evaluation stack or into native registers, + // so we need to make sure to remove the node. In some cases the node is after us in the + // iteration order and will be automatically removed, but we may have already iterated + // over it without removing it, so it's necessary to clean up here. + // Removing the unused lcl_var/lcl_fld before iterating over it causes crashes in emit. + JITDUMP("Removing dead store data:\n"); + DISPNODE(data); + blockRange.Remove(data); + } } } } From 26dc2c3c1569601bc33c42eabcf8d8beb5304577 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Wed, 8 Apr 2026 16:17:56 -0700 Subject: [PATCH 06/11] jit-format --- src/coreclr/jit/liveness.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index 23a1f680a7a513..fcbfec14ca2822 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -2332,8 +2332,8 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR noway_assert(VarSetOps::IsSubset(m_compiler, keepAliveVars, life)); GenTree* mostRecentLocalVarOrField = nullptr; - LIR::Range& blockRange = LIR::AsRange(block); - GenTree* firstNode = blockRange.FirstNode(); + LIR::Range& blockRange = LIR::AsRange(block); + GenTree* firstNode = blockRange.FirstNode(); if (firstNode == nullptr) { return; @@ -2467,12 +2467,13 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR } else if (data == mostRecentLocalVarOrField) { - // The unused lcl_var or lcl_field on the rhs of a removed block store may be a struct - // which cannot always be loaded onto the Wasm evaluation stack or into native registers, - // so we need to make sure to remove the node. In some cases the node is after us in the - // iteration order and will be automatically removed, but we may have already iterated - // over it without removing it, so it's necessary to clean up here. - // Removing the unused lcl_var/lcl_fld before iterating over it causes crashes in emit. + // The unused lcl_var or lcl_field on the rhs of a removed block store may be a + // struct which cannot always be loaded onto the Wasm evaluation stack or into + // native registers, so we need to make sure to remove the node. In some cases the + // node is after us in the iteration order and will be automatically removed, but we + // may have already iterated over it without removing it, so it's necessary to clean + // up here. Removing the unused lcl_var/lcl_fld before iterating over it causes + // crashes in emit. JITDUMP("Removing dead store data:\n"); DISPNODE(data); blockRange.Remove(data); From a531c3fb2b4036b130935a44f760aec27464ee7c Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Fri, 10 Apr 2026 13:11:09 -0700 Subject: [PATCH 07/11] Implement without local var --- src/coreclr/jit/liveness.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index fcbfec14ca2822..35f695ac654401 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -2331,7 +2331,6 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR { noway_assert(VarSetOps::IsSubset(m_compiler, keepAliveVars, life)); - GenTree* mostRecentLocalVarOrField = nullptr; LIR::Range& blockRange = LIR::AsRange(block); GenTree* firstNode = blockRange.FirstNode(); if (firstNode == nullptr) @@ -2395,7 +2394,6 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR { GenTreeLclVarCommon* const lclVarNode = node->AsLclVarCommon(); LclVarDsc& varDsc = m_compiler->lvaTable[lclVarNode->GetLclNum()]; - mostRecentLocalVarOrField = node; if (TLiveness::EliminateDeadCode && node->IsUnusedValue()) { @@ -2454,6 +2452,7 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR if (TryRemoveDeadStoreLIR(store, node->AsLclVarCommon(), block)) { + JITDUMP("Removing dead LclVar address:\n"); DISPNODE(node); blockRange.Remove(node); @@ -2465,7 +2464,7 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR { Lowering::TransformUnusedIndirection(data->AsIndir(), m_compiler, block); } - else if (data == mostRecentLocalVarOrField) + else if (data->OperIs(GT_LCL_VAR, GT_LCL_FLD)) { // The unused lcl_var or lcl_field on the rhs of a removed block store may be a // struct which cannot always be loaded onto the Wasm evaluation stack or into @@ -2476,6 +2475,10 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR // crashes in emit. JITDUMP("Removing dead store data:\n"); DISPNODE(data); + if (next == data) + next = data->gtPrev; + if (end == data) + end = data->gtNext; blockRange.Remove(data); } } From 944ba1130fd4228af4d2c726c18864692c05e468 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Fri, 10 Apr 2026 13:15:34 -0700 Subject: [PATCH 08/11] jit-format --- src/coreclr/jit/liveness.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index 35f695ac654401..e992ba068ef796 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -2331,8 +2331,8 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR { noway_assert(VarSetOps::IsSubset(m_compiler, keepAliveVars, life)); - LIR::Range& blockRange = LIR::AsRange(block); - GenTree* firstNode = blockRange.FirstNode(); + LIR::Range& blockRange = LIR::AsRange(block); + GenTree* firstNode = blockRange.FirstNode(); if (firstNode == nullptr) { return; From c3e4600ef58b36d9267455e9e7d910099b092a9b Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Fri, 10 Apr 2026 13:23:52 -0700 Subject: [PATCH 09/11] Fix incorrect comment; use Delete instead of Remove like other code does --- src/coreclr/jit/liveness.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index e992ba068ef796..75d49f68e93d79 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -2471,15 +2471,15 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR // native registers, so we need to make sure to remove the node. In some cases the // node is after us in the iteration order and will be automatically removed, but we // may have already iterated over it without removing it, so it's necessary to clean - // up here. Removing the unused lcl_var/lcl_fld before iterating over it causes - // crashes in emit. + // up here. JITDUMP("Removing dead store data:\n"); DISPNODE(data); if (next == data) next = data->gtPrev; if (end == data) end = data->gtNext; - blockRange.Remove(data); + blockRange.Delete(m_compiler, block, data); + // fgStmtRemoved was already set by TryRemoveDeadStoreLIR } } } From 5b48943bd0c90d4e9f1403f5edbbf12838c8d96f Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Fri, 10 Apr 2026 13:25:30 -0700 Subject: [PATCH 10/11] Address copilot feedback --- src/coreclr/jit/codegenwasm.cpp | 3 +-- src/coreclr/jit/liveness.cpp | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 14bedae2e61534..84ae026acf55be 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2213,9 +2213,8 @@ void CodeGen::genCodeForLclVar(GenTreeLclVar* tree) // Block stores can get pruned from LIR, and when that happens a LclVar node of TYP_STRUCT can get left behind // in LIR. Its RegisterType will be TYP_UNDEF, but it will be flagged with IsUnusedValue to tell us to ignore // it. - if (type == TYP_UNDEF) + if (tree->IsUnusedValue()) { - assert(tree->IsUnusedValue()); return; } GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetFramePointerRegIndex()); diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index 75d49f68e93d79..3174ac2ee4c7e8 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -2475,9 +2475,13 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR JITDUMP("Removing dead store data:\n"); DISPNODE(data); if (next == data) + { next = data->gtPrev; + } if (end == data) + { end = data->gtNext; + } blockRange.Delete(m_compiler, block, data); // fgStmtRemoved was already set by TryRemoveDeadStoreLIR } From 422a94203b7fcfdecf18d4a9d23c0acc8b5c71aa Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Sun, 12 Apr 2026 15:24:12 -0700 Subject: [PATCH 11/11] Address PR feedback: Revert unused value change and assert end instead of handling it --- src/coreclr/jit/codegenwasm.cpp | 7 ------- src/coreclr/jit/liveness.cpp | 5 +---- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 84ae026acf55be..3dacd2e5e837e0 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2210,13 +2210,6 @@ void CodeGen::genCodeForLclVar(GenTreeLclVar* tree) if (!varDsc->lvIsRegCandidate()) { var_types type = varDsc->GetRegisterType(tree); - // Block stores can get pruned from LIR, and when that happens a LclVar node of TYP_STRUCT can get left behind - // in LIR. Its RegisterType will be TYP_UNDEF, but it will be flagged with IsUnusedValue to tell us to ignore - // it. - if (tree->IsUnusedValue()) - { - return; - } GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetFramePointerRegIndex()); GetEmitter()->emitIns_S(ins_Load(type), emitTypeSize(type), tree->GetLclNum(), 0); WasmProduceReg(tree); diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index 3174ac2ee4c7e8..b0a5bc86eef185 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -2478,10 +2478,7 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR { next = data->gtPrev; } - if (end == data) - { - end = data->gtNext; - } + assert(end != data); blockRange.Delete(m_compiler, block, data); // fgStmtRemoved was already set by TryRemoveDeadStoreLIR }