From cdf10a52c556cdbb81e97b032d080cd1d4ff8e66 Mon Sep 17 00:00:00 2001 From: David Hartglass Date: Thu, 16 Apr 2026 16:33:37 -0700 Subject: [PATCH 1/7] add testcase --- .../JitBlue/Runtime_126457/Runtime_126457.il | 92 +++++++++++++++++++ .../Runtime_126457/Runtime_126457.ilproj | 14 +++ 2 files changed, 106 insertions(+) create mode 100644 src/tests/JIT/Regression/JitBlue/Runtime_126457/Runtime_126457.il create mode 100644 src/tests/JIT/Regression/JitBlue/Runtime_126457/Runtime_126457.ilproj diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_126457/Runtime_126457.il b/src/tests/JIT/Regression/JitBlue/Runtime_126457/Runtime_126457.il new file mode 100644 index 00000000000000..66b66bc4b701c8 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_126457/Runtime_126457.il @@ -0,0 +1,92 @@ +.assembly extern System.Runtime +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A) +} + +.assembly Runtime_126457 { } +.module Runtime_126457.dll + +.class interface public abstract auto ansi ISite +{ + .method public hidebysig newslot abstract virtual + instance bool tailcall() cil managed + { + } +} + +.class public auto ansi beforefieldinit Component + extends [System.Runtime]System.Object +{ + .field family class ISite _site + + .method family hidebysig newslot virtual + instance bool callee() cil managed + { + .maxstack 8 + ldarg.0 + ldfld class ISite Component::_site + dup + brtrue.s CALL + pop + ldc.i4.0 + br.s DONE + CALL: + callvirt instance bool ISite::tailcall() + DONE: + ret + } + + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + ldarg.0 + call instance void [System.Runtime]System.Object::.ctor() + ret + } +} + +.class public auto ansi beforefieldinit DerivedComponent + extends Component +{ + .method public hidebysig virtual + instance bool caller() cil managed + { + .maxstack 8 + ldarg.0 + call instance bool Component::callee() + ret + } + + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + ldarg.0 + call instance void Component::.ctor() + ret + } +} + +.class private auto ansi beforefieldinit Program + extends [System.Runtime]System.Object +{ + .method private hidebysig static int32 + Main() cil managed + { + .entrypoint + .maxstack 1 + newobj instance void DerivedComponent::.ctor() + call bool Program::Test(class DerivedComponent) + pop + ldc.i4.s 100 + ret + } + + .method private hidebysig static bool + Test(class DerivedComponent c) cil managed noinlining + { + .maxstack 8 + ldarg.0 + callvirt instance bool DerivedComponent::caller() + ret + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_126457/Runtime_126457.ilproj b/src/tests/JIT/Regression/JitBlue/Runtime_126457/Runtime_126457.ilproj new file mode 100644 index 00000000000000..35c76de2c3b163 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_126457/Runtime_126457.ilproj @@ -0,0 +1,14 @@ + + + true + + + None + True + + + + + + + From e5fde6bc9f07d53f174a71954d31ed09c9d00888 Mon Sep 17 00:00:00 2001 From: David Hartglass Date: Thu, 16 Apr 2026 17:36:16 -0700 Subject: [PATCH 2/7] initial bugfix --- src/coreclr/jit/morph.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 4f370aae0883d5..5a2ccad021f811 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5040,6 +5040,14 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call) { assert(ValidateUse(tree) && "Expected use of local to be tailcall value"); } + else if (tree->OperIs(GT_CAST)) + { + // A normalizing cast (e.g., int -> ubyte -> int for bool returns) + // in a shared return block is safe to ignore. + assert(IsNormalizingReturnCast(tree) && + ValidateUse(tree->AsCast()->CastOp()) && + "Expected normalizing cast of tailcall result"); + } else if (IsCommaNop(tree)) { // COMMA(NOP,NOP) @@ -5063,8 +5071,27 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call) return node->AsOp()->gtGetOp1()->OperIs(GT_NOP) && node->AsOp()->gtGetOp2()->OperIs(GT_NOP); } + static bool IsNormalizingReturnCast(GenTree* node) + { + if (!node->OperIs(GT_CAST) || node->gtOverflow()) + { + return false; + } + + GenTreeCast* cast = node->AsCast(); + return varTypeIsSmall(cast->gtCastType) && + genActualType(cast->CastOp()) == TYP_INT && + genActualType(cast) == TYP_INT; + } + bool ValidateUse(GenTree* node) { + // Peel through normalizing casts (e.g., int -> ubyte -> int for bool returns). + while (IsNormalizingReturnCast(node)) + { + node = node->AsCast()->CastOp(); + } + if (m_lclNum != BAD_VAR_NUM) { return node->OperIs(GT_LCL_VAR) && (node->AsLclVar()->GetLclNum() == m_lclNum); From 26f5dc176e1120a63dec146183619f6608787fa2 Mon Sep 17 00:00:00 2001 From: David Hartglass Date: Thu, 16 Apr 2026 23:45:43 -0700 Subject: [PATCH 3/7] cleanup --- src/coreclr/jit/morph.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 5a2ccad021f811..211232095096ae 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5042,9 +5042,11 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call) } else if (tree->OperIs(GT_CAST)) { - // A normalizing cast (e.g., int -> ubyte -> int for bool returns) - // in a shared return block is safe to ignore. - assert(IsNormalizingReturnCast(tree) && + // The inliner can insert small-type-normalizing casts before the return + // (int -> ubyte -> int for bool-return-calls, for example) + // In the jit the callee is responsible for normalizing, so a tailcall + // can freely bypass this extra cast + assert(IsNormalizingCast(tree) && ValidateUse(tree->AsCast()->CastOp()) && "Expected normalizing cast of tailcall result"); } @@ -5071,23 +5073,22 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call) return node->AsOp()->gtGetOp1()->OperIs(GT_NOP) && node->AsOp()->gtGetOp2()->OperIs(GT_NOP); } - static bool IsNormalizingReturnCast(GenTree* node) + bool IsNormalizingCast(GenTree* node) { - if (!node->OperIs(GT_CAST) || node->gtOverflow()) + if (!node->OperIs(GT_CAST)) { return false; } GenTreeCast* cast = node->AsCast(); - return varTypeIsSmall(cast->gtCastType) && + return varTypeIsSmall(cast->CastToType()) && genActualType(cast->CastOp()) == TYP_INT && genActualType(cast) == TYP_INT; } bool ValidateUse(GenTree* node) { - // Peel through normalizing casts (e.g., int -> ubyte -> int for bool returns). - while (IsNormalizingReturnCast(node)) + if (IsNormalizingCast(node)) { node = node->AsCast()->CastOp(); } From 95f4d60a6c6be903e02d55ac40e23a3e8ed9eb71 Mon Sep 17 00:00:00 2001 From: David Hartglass Date: Fri, 17 Apr 2026 11:25:13 -0700 Subject: [PATCH 4/7] use fgCastNeeded instead of custom impl --- src/coreclr/jit/morph.cpp | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 211232095096ae..34873fac813449 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5046,9 +5046,7 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call) // (int -> ubyte -> int for bool-return-calls, for example) // In the jit the callee is responsible for normalizing, so a tailcall // can freely bypass this extra cast - assert(IsNormalizingCast(tree) && - ValidateUse(tree->AsCast()->CastOp()) && - "Expected normalizing cast of tailcall result"); + assert(ValidateUse(tree) && "Unexpected non-normalizing cast of tailcall result"); } else if (IsCommaNop(tree)) { @@ -5073,22 +5071,10 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call) return node->AsOp()->gtGetOp1()->OperIs(GT_NOP) && node->AsOp()->gtGetOp2()->OperIs(GT_NOP); } - bool IsNormalizingCast(GenTree* node) - { - if (!node->OperIs(GT_CAST)) - { - return false; - } - - GenTreeCast* cast = node->AsCast(); - return varTypeIsSmall(cast->CastToType()) && - genActualType(cast->CastOp()) == TYP_INT && - genActualType(cast) == TYP_INT; - } - bool ValidateUse(GenTree* node) { - if (IsNormalizingCast(node)) + if (node->OperIs(GT_CAST) && + !m_compiler->fgCastNeeded(m_tailcall, node->AsCast()->CastToType())) { node = node->AsCast()->CastOp(); } From c22ab399f14f98ae789eb78bde2700ef1d1018e4 Mon Sep 17 00:00:00 2001 From: David Hartglass Date: Fri, 17 Apr 2026 11:39:37 -0700 Subject: [PATCH 5/7] clang format --- src/coreclr/jit/morph.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 34873fac813449..20592c74885ad7 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5073,8 +5073,7 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call) bool ValidateUse(GenTree* node) { - if (node->OperIs(GT_CAST) && - !m_compiler->fgCastNeeded(m_tailcall, node->AsCast()->CastToType())) + if (node->OperIs(GT_CAST) && !m_compiler->fgCastNeeded(m_tailcall, node->AsCast()->CastToType())) { node = node->AsCast()->CastOp(); } From 90dc30e7fd7657399d987440fadc188ff7f692ee Mon Sep 17 00:00:00 2001 From: David Hartglass Date: Fri, 17 Apr 2026 13:40:47 -0700 Subject: [PATCH 6/7] move castneeded validation back into the visit method --- src/coreclr/jit/morph.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 20592c74885ad7..5b9d769d2fc9d1 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5046,7 +5046,9 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call) // (int -> ubyte -> int for bool-return-calls, for example) // In the jit the callee is responsible for normalizing, so a tailcall // can freely bypass this extra cast - assert(ValidateUse(tree) && "Unexpected non-normalizing cast of tailcall result"); + assert(!m_compiler->fgCastNeeded(m_tailcall, tree->AsCast()->CastToType()) && + ValidateUse(tree->AsCast()->CastOp()) && + "Expected normalizing cast of tailcall result"); } else if (IsCommaNop(tree)) { @@ -5073,7 +5075,7 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call) bool ValidateUse(GenTree* node) { - if (node->OperIs(GT_CAST) && !m_compiler->fgCastNeeded(m_tailcall, node->AsCast()->CastToType())) + if (node->OperIs(GT_CAST)) { node = node->AsCast()->CastOp(); } From 1b62927be7656771fce93d97df1e4eb1aa9d32a3 Mon Sep 17 00:00:00 2001 From: David Hartglass Date: Fri, 17 Apr 2026 13:42:20 -0700 Subject: [PATCH 7/7] clang format --- src/coreclr/jit/morph.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 5b9d769d2fc9d1..87fca6926ca835 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5047,8 +5047,7 @@ void Compiler::fgValidateIRForTailCall(GenTreeCall* call) // In the jit the callee is responsible for normalizing, so a tailcall // can freely bypass this extra cast assert(!m_compiler->fgCastNeeded(m_tailcall, tree->AsCast()->CastToType()) && - ValidateUse(tree->AsCast()->CastOp()) && - "Expected normalizing cast of tailcall result"); + ValidateUse(tree->AsCast()->CastOp()) && "Expected normalizing cast of tailcall result"); } else if (IsCommaNop(tree)) {