From: SingleAccretion <62474226+SingleAccretion@users.noreply.github.com> Date: Tue, 26 Jul 2022 19:30:12 +0000 (+0300) Subject: Consolidate importer spilling code V2 (#72744) X-Git-Tag: accepted/tizen/unified/riscv/20231226.055536~7541 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=cea17b2ac2886a154aa813bcfa2fac386ed9f10a;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Consolidate importer spilling code V2 (#72744) * Consolidate importer spilling code (#72291) * Add tests * Fix losing GLOB_REF on the LHS The comment states we don't need it, which is incorrect. Diffs are improvements because we block forward substitution of calls into "ASG(BLK(ADDR(LCL_VAR, ...)))", which allows morph to leave the "can be replaced with its field" local alone. * Prospective fix Spill "glob refs" on stores to "aliased" locals. * Delete now-not-necessary code * Fix up asserts * Clean out '(unsigned)CHECK_SPILL_ALL/NONE' casts * Don't manually spill for 'st[s]fld' * Revert 'Clean out '(unsigned)CHECK_SPILL_ALL/NONE' casts' * Fix assignments done via return buffers The mistake in logic was that the only trees which could modify unaliased locals are assignments, which is not true, calls can do that as well. One day we will move the return buffer handling out of importer, but until then, special handling is required. An alternative fix would have been to bring back the explicit "impSpillLclRefs" to "stloc/starg" code, but that would contradict the overall goal of consolidating the spilling logic. --- diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 0315e5e..f80271a 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -3797,11 +3797,8 @@ protected: Statement* impLastStmt; // The last statement for the current BB. public: - enum - { - CHECK_SPILL_ALL = -1, - CHECK_SPILL_NONE = -2 - }; + static const unsigned CHECK_SPILL_ALL = static_cast(-1); + static const unsigned CHECK_SPILL_NONE = static_cast(-2); void impBeginTreeList(); void impEndTreeList(BasicBlock* block, Statement* firstStmt, Statement* lastStmt); @@ -4001,7 +3998,7 @@ private: void impSpillSpecialSideEff(); void impSpillSideEffect(bool spillGlobEffects, unsigned chkLevel DEBUGARG(const char* reason)); void impSpillSideEffects(bool spillGlobEffects, unsigned chkLevel DEBUGARG(const char* reason)); - void impSpillLclRefs(unsigned lclNum); + void impSpillLclRefs(unsigned lclNum, unsigned chkLevel); BasicBlock* impPushCatchArgOnStack(BasicBlock* hndBlk, CORINFO_CLASS_HANDLE clsHnd, bool isSingleBlockFilter); diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 3f1c97b..dd7b9c7 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -453,25 +453,23 @@ inline void Compiler::impAppendStmtCheck(Statement* stmt, unsigned chkLevel) } } - if (tree->gtOper == GT_ASG) + if (tree->OperIs(GT_ASG)) { // For an assignment to a local variable, all references of that // variable have to be spilled. If it is aliased, all calls and // indirect accesses have to be spilled - if (tree->AsOp()->gtOp1->gtOper == GT_LCL_VAR) + if (tree->AsOp()->gtOp1->OperIsLocal()) { unsigned lclNum = tree->AsOp()->gtOp1->AsLclVarCommon()->GetLclNum(); for (unsigned level = 0; level < chkLevel; level++) { - assert(!gtHasRef(verCurrentState.esStack[level].val, lclNum)); - assert(!lvaTable[lclNum].IsAddressExposed() || - (verCurrentState.esStack[level].val->gtFlags & GTF_SIDE_EFFECT) == 0); + GenTree* stkTree = verCurrentState.esStack[level].val; + assert(!gtHasRef(stkTree, lclNum) || impIsInvariant(stkTree)); + assert(!lvaTable[lclNum].IsAddressExposed() || ((stkTree->gtFlags & GTF_SIDE_EFFECT) == 0)); } } - // If the access may be to global memory, all side effects have to be spilled. - else if (tree->AsOp()->gtOp1->gtFlags & GTF_GLOB_REF) { for (unsigned level = 0; level < chkLevel; level++) @@ -490,7 +488,7 @@ inline void Compiler::impAppendStmtCheck(Statement* stmt, unsigned chkLevel) // Arguments: // stmt - The statement to add. // chkLevel - [0..chkLevel) is the portion of the stack which we will check -// for interference with stmt and spill if needed. +// for interference with stmt and spilled if needed. // checkConsumedDebugInfo - Whether to check for consumption of impCurStmtDI. impCurStmtDI // marks the debug info of the current boundary and is set when we // start importing IL at that boundary. If this parameter is true, @@ -509,61 +507,71 @@ void Compiler::impAppendStmt(Statement* stmt, unsigned chkLevel, bool checkConsu { assert(chkLevel <= verCurrentState.esStackDepth); - /* If the statement being appended has any side-effects, check the stack - to see if anything needs to be spilled to preserve correct ordering. */ - + // If the statement being appended has any side-effects, check the stack to see if anything + // needs to be spilled to preserve correct ordering. + // GenTree* expr = stmt->GetRootNode(); GenTreeFlags flags = expr->gtFlags & GTF_GLOB_EFFECT; - // Assignment to (unaliased) locals don't count as a side-effect as - // we handle them specially using impSpillLclRefs(). Temp locals should - // be fine too. - - if ((expr->gtOper == GT_ASG) && (expr->AsOp()->gtOp1->gtOper == GT_LCL_VAR) && - ((expr->AsOp()->gtOp1->gtFlags & GTF_GLOB_REF) == 0) && !gtHasLocalsWithAddrOp(expr->AsOp()->gtOp2)) + // Assignments to unaliased locals require special handling. Here, we look for trees that + // can modify them and spill the references. In doing so, we make two assumptions: + // + // 1. All locals which can be modified indirectly are marked as address-exposed or with + // "lvHasLdAddrOp" -- we will rely on "impSpillSideEffects(spillGlobEffects: true)" + // below to spill them. + // 2. Trees that assign to unaliased locals are always top-level (this avoids having to + // walk down the tree here), and are a subset of what is recognized here. + // + // If any of the above are violated (say for some temps), the relevant code must spill + // things manually. + // + LclVarDsc* dstVarDsc = nullptr; + if (expr->OperIs(GT_ASG) && expr->AsOp()->gtOp1->OperIsLocal()) { - GenTreeFlags op2Flags = expr->AsOp()->gtOp2->gtFlags & GTF_GLOB_EFFECT; - assert(flags == (op2Flags | GTF_ASG)); - flags = op2Flags; + dstVarDsc = lvaGetDesc(expr->AsOp()->gtOp1->AsLclVarCommon()); } - - if (flags != 0) + else if (expr->OperIs(GT_CALL, GT_RET_EXPR)) // The special case of calls with return buffers. { - bool spillGlobEffects = false; + GenTree* call = expr->OperIs(GT_RET_EXPR) ? expr->AsRetExpr()->gtInlineCandidate : expr; - if ((flags & GTF_CALL) != 0) + if (call->TypeIs(TYP_VOID) && call->AsCall()->TreatAsShouldHaveRetBufArg(this)) { - // If there is a call, we have to spill global refs - spillGlobEffects = true; - } - else if (!expr->OperIs(GT_ASG)) - { - if ((flags & GTF_ASG) != 0) + GenTree* retBuf; + if (call->AsCall()->ShouldHaveRetBufArg()) { - // The expression is not an assignment node but it has an assignment side effect, it - // must be an atomic op, HW intrinsic or some other kind of node that stores to memory. - // Since we don't know what it assigns to, we need to spill global refs. - spillGlobEffects = true; + assert(call->AsCall()->gtArgs.HasRetBuffer()); + retBuf = call->AsCall()->gtArgs.GetRetBufferArg()->GetNode(); } - } - else - { - GenTree* lhs = expr->gtGetOp1(); - GenTree* rhs = expr->gtGetOp2(); - - if (((rhs->gtFlags | lhs->gtFlags) & GTF_ASG) != 0) + else { - // Either side of the assignment node has an assignment side effect. - // Since we don't know what it assigns to, we need to spill global refs. - spillGlobEffects = true; + assert(!call->AsCall()->gtArgs.HasThisPointer()); + retBuf = call->AsCall()->gtArgs.GetArgByIndex(0)->GetNode(); } - else if ((lhs->gtFlags & GTF_GLOB_REF) != 0) + + assert(retBuf->TypeIs(TYP_I_IMPL, TYP_BYREF)); + + GenTreeLclVarCommon* lclNode = retBuf->IsLocalAddrExpr(); + if (lclNode != nullptr) { - spillGlobEffects = true; + dstVarDsc = lvaGetDesc(lclNode); } } + } + + if ((dstVarDsc != nullptr) && !dstVarDsc->IsAddressExposed() && !dstVarDsc->lvHasLdAddrOp) + { + impSpillLclRefs(lvaGetLclNum(dstVarDsc), chkLevel); + + if (expr->OperIs(GT_ASG)) + { + // For assignments, limit the checking to what the RHS could modify/interfere with. + flags = expr->AsOp()->gtOp2->gtFlags & GTF_GLOB_EFFECT; + } + } - impSpillSideEffects(spillGlobEffects, chkLevel DEBUGARG("impAppendStmt")); + if (flags != 0) + { + impSpillSideEffects((flags & (GTF_ASG | GTF_CALL)) != 0, chkLevel DEBUGARG("impAppendStmt")); } else { @@ -1500,11 +1508,6 @@ GenTree* Compiler::impAssignStructPtr(GenTree* destAddr, { dest = gtNewObjNode(structHnd, destAddr); gtSetObjGcInfo(dest->AsObj()); - // Although an obj as a call argument was always assumed to be a globRef - // (which is itself overly conservative), that is not true of the operands - // of a block assignment. - dest->gtFlags &= ~GTF_GLOB_REF; - dest->gtFlags |= (destAddr->gtFlags & GTF_GLOB_REF); } else { @@ -2357,14 +2360,6 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken return gtNewLclvNode(tmp, TYP_I_IMPL); } -/****************************************************************************** - * Spills the stack at verCurrentState.esStack[level] and replaces it with a temp. - * If tnum!=BAD_VAR_NUM, the temp var used to replace the tree is tnum, - * else, grab a new temp. - * For structs (which can be pushed on the stack using obj, etc), - * special handling is needed - */ - struct RecursiveGuard { public: @@ -2583,21 +2578,27 @@ inline void Compiler::impSpillSpecialSideEff() } } -/***************************************************************************** - * - * If the stack contains any trees with references to local #lclNum, assign - * those trees to temps and replace their place on the stack with refs to - * their temps. - */ - -void Compiler::impSpillLclRefs(unsigned lclNum) +//------------------------------------------------------------------------ +// impSpillLclRefs: Spill all trees referencing the given local. +// +// Arguments: +// lclNum - The local's number +// chkLevel - Height (exclusive) of the portion of the stack to check +// +void Compiler::impSpillLclRefs(unsigned lclNum, unsigned chkLevel) { - /* Before we make any appends to the tree list we must spill the - * "special" side effects (GTF_ORDER_SIDEEFF) - GT_CATCH_ARG */ - + // Before we make any appends to the tree list we must spill the + // "special" side effects (GTF_ORDER_SIDEEFF) - GT_CATCH_ARG. impSpillSpecialSideEff(); - for (unsigned level = 0; level < verCurrentState.esStackDepth; level++) + if (chkLevel == CHECK_SPILL_ALL) + { + chkLevel = verCurrentState.esStackDepth; + } + + assert(chkLevel <= verCurrentState.esStackDepth); + + for (unsigned level = 0; level < chkLevel; level++) { GenTree* tree = verCurrentState.esStack[level].val; @@ -13195,56 +13196,14 @@ void Compiler::impImportBlockCode(BasicBlock* block) goto DECODE_OPCODE; SPILL_APPEND: - - // We need to call impSpillLclRefs() for a struct type lclVar. - // This is because there may be loads of that lclVar on the evaluation stack, and - // we need to ensure that those loads are completed before we modify it. - if ((op1->OperGet() == GT_ASG) && varTypeIsStruct(op1->gtGetOp1())) - { - GenTree* lhs = op1->gtGetOp1(); - GenTreeLclVarCommon* lclVar = nullptr; - if (lhs->gtOper == GT_LCL_VAR) - { - lclVar = lhs->AsLclVarCommon(); - } - else if (lhs->OperIsBlk()) - { - // Check if LHS address is within some struct local, to catch - // cases where we're updating the struct by something other than a stfld - GenTree* addr = lhs->AsBlk()->Addr(); - - // Catches ADDR(LCL_VAR), or ADD(ADDR(LCL_VAR),CNS_INT)) - lclVar = addr->IsLocalAddrExpr(); - - // Catches ADDR(FIELD(... ADDR(LCL_VAR))) - if (lclVar == nullptr) - { - GenTree* lclTree = nullptr; - if (impIsAddressInLocal(addr, &lclTree)) - { - lclVar = lclTree->AsLclVarCommon(); - } - } - } - if (lclVar != nullptr) - { - impSpillLclRefs(lclVar->GetLclNum()); - } - } - - /* Append 'op1' to the list of statements */ impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtDI); goto DONE_APPEND; APPEND: - - /* Append 'op1' to the list of statements */ - impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtDI); goto DONE_APPEND; DONE_APPEND: - #ifdef DEBUG // Remember at which BC offset the tree was finished impNoteLastILoffs(); @@ -13522,25 +13481,16 @@ void Compiler::impImportBlockCode(BasicBlock* block) } } - /* Create the assignment node */ - op2 = gtNewLclvNode(lclNum, lclTyp DEBUGARG(opcodeOffs + sz + 1)); - /* If the local is aliased or pinned, we need to spill calls and - indirections from the stack. */ - - if ((lvaTable[lclNum].IsAddressExposed() || lvaTable[lclNum].lvHasLdAddrOp || - lvaTable[lclNum].lvPinned) && - (verCurrentState.esStackDepth > 0)) + // Stores to pinned locals can have the implicit side effect of "unpinning", so we must spill + // things that could depend on the pin. TODO-Bug: which can actually be anything, including + // unpinned unaliased locals, not just side-effecting trees. + if (lvaTable[lclNum].lvPinned) { - impSpillSideEffects(false, - (unsigned)CHECK_SPILL_ALL DEBUGARG("Local could be aliased or is pinned")); + impSpillSideEffects(false, CHECK_SPILL_ALL DEBUGARG("Spill before store to pinned local")); } - /* Spill any refs to the local from the stack */ - - impSpillLclRefs(lclNum); - // We can generate an assignment to a TYP_FLOAT from a TYP_DOUBLE // We insert a cast to the dest 'op2' type // @@ -13552,13 +13502,12 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (varTypeIsStruct(lclTyp)) { - op1 = impAssignStruct(op2, op1, clsHnd, (unsigned)CHECK_SPILL_ALL); + op1 = impAssignStruct(op2, op1, clsHnd, CHECK_SPILL_ALL); } else { op1 = gtNewAssignNode(op2, op1); } - goto SPILL_APPEND; case CEE_LDLOCA: @@ -15172,6 +15121,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) #endif op1 = gtNewOperNode(GT_IND, lclTyp, op1); + op1->gtFlags |= GTF_EXCEPT | GTF_GLOB_REF; if (prefixFlags & PREFIX_VOLATILE) { @@ -15187,15 +15137,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) } op1 = gtNewAssignNode(op1, op2); - op1->gtFlags |= GTF_EXCEPT | GTF_GLOB_REF; - - // Spill side-effects AND global-data-accesses - if (verCurrentState.esStackDepth > 0) - { - impSpillSideEffects(true, (unsigned)CHECK_SPILL_ALL DEBUGARG("spill side effects before STIND")); - } - - goto APPEND; + goto SPILL_APPEND; case CEE_LDIND_I1: lclTyp = TYP_BYTE; @@ -16352,11 +16294,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) assert(!"Unexpected fieldAccessor"); } - // "impAssignStruct" will back-substitute the field address tree into calls that return things via - // return buffers, so we have to delay calling it until after we have spilled everything needed. - bool deferStructAssign = (lclTyp == TYP_STRUCT); - - if (!deferStructAssign) + if (lclTyp != TYP_STRUCT) { assert(op1->OperIs(GT_FIELD, GT_IND)); @@ -16445,8 +16383,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) op1 = gtNewAssignNode(op1, op2); } - /* Check if the class needs explicit initialization */ - + // Check if the class needs explicit initialization. if (fieldInfo.fieldFlags & CORINFO_FLG_FIELD_INITCLASS) { GenTree* helperNode = impInitClass(&resolvedToken); @@ -16460,16 +16397,12 @@ void Compiler::impImportBlockCode(BasicBlock* block) } } - // An indirect store such as "st[s]fld" interferes with indirect accesses, so we must spill - // global refs and potentially aliased locals. - impSpillSideEffects(true, (unsigned)CHECK_SPILL_ALL DEBUGARG("spill side effects before STFLD")); - - if (deferStructAssign) + if (lclTyp == TYP_STRUCT) { - op1 = impAssignStruct(op1, op2, clsHnd, (unsigned)CHECK_SPILL_ALL); + op1 = impAssignStruct(op1, op2, clsHnd, CHECK_SPILL_ALL); } + goto SPILL_APPEND; } - goto APPEND; case CEE_NEWARR: { diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_72133/Runtime_72133.il b/src/tests/JIT/Regression/JitBlue/Runtime_72133/Runtime_72133.il new file mode 100644 index 0000000..b67d10f --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_72133/Runtime_72133.il @@ -0,0 +1,411 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +.assembly extern System.Runtime { } +.assembly extern System.Console { } +.assembly extern System.Numerics.Vectors { } + +.assembly Runtime_72133 { } + +#define TRUE "1" +#define FALSE "0" + +.typedef [System.Numerics.Vectors]System.Numerics.Vector2 as Vector2 + +.class Runtime_72133 extends [System.Runtime]System.Object +{ + .method static int32 Main() + { + .entrypoint + .locals (int32 result) + + ldc.i4 100 + stloc result + + call bool .this::ProblemWithStructStObj() + brfalse VECTOR_STOBJ + + ldloca result + ldstr "STRUCT_STOBJ failed" + call void .this::ReportFailure(int32*, string) + + VECTOR_STOBJ: + call bool .this::ProblemWithVectorStObj() + brfalse CPBLK + + ldloca result + ldstr "VECTOR_STOBJ failed" + call void .this::ReportFailure(int32*, string) + + CPBLK: + call bool .this::ProblemWithCpBlk() + brfalse INITBLK + + ldloca result + ldstr "CPBLK failed" + call void .this::ReportFailure(int32*, string) + + INITBLK: + call bool .this::ProblemWithInitBlk() + brfalse INITOBJ + + ldloca result + ldstr "INITBLK failed" + call void .this::ReportFailure(int32*, string) + + INITOBJ: + call bool .this::ProblemWithInitObj() + brfalse DIRECT_INITOBJ + + ldloca result + ldstr "INITOBJ failed" + call void .this::ReportFailure(int32*, string) + + DIRECT_INITOBJ: + call bool .this::ProblemWithDirectInitObj() + brfalse RET_BUF_CALL + + ldloca result + ldstr "DIRECT_INITOBJ failed" + call void .this::ReportFailure(int32*, string) + + RET_BUF_CALL: + call bool .this::ProblemWithRetBufCall() + brfalse RETURN + + ldloca result + ldstr "RET_BUF_CALL failed" + call void .this::ReportFailure(int32*, string) + + RETURN: + ldloc result + ret + } + + .method private static bool ProblemWithStructStObj() noinlining + { + .locals (int32 a, int32 b, int32 offs) + + ldc.i4 1 + call !!0 .this::Get(!!0) + stloc a + + ldc.i4 2 + call !!0 .this::Get(!!0) + stloc b + + ldc.i4 0 + call !!0 .this::Get(!!0) + stloc offs + + ldloc a + + ldloca a + ldloc offs + add + ldloca b + ldobj StructWithInt + stobj StructWithInt + + ldc.i4 1 + bne.un FAILURE + ldc.i4 FALSE + ret + + FAILURE: + ldc.i4 TRUE + ret + } + + .method private static bool ProblemWithVectorStObj() noinlining + { + .locals (int64 a, int64 b, int32 offs) + + ldc.i8 1 + call !!0 .this::Get(!!0) + stloc a + + ldc.i8 2 + call !!0 .this::Get(!!0) + stloc b + + ldc.i4 0 + call !!0 .this::Get(!!0) + stloc offs + + ldloc a + + ldloca a + ldloc offs + add + ldloca b + ldobj Vector2 + stobj Vector2 + + ldc.i8 1 + bne.un FAILURE + ldc.i4 FALSE + ret + + FAILURE: + ldc.i4 TRUE + ret + } + + .method private static bool ProblemWithCpBlk() noinlining + { + .locals (int32 a, int32 b, int32 offs) + + ldc.i4 1 + call !!0 .this::Get(!!0) + stloc a + + ldc.i4 2 + call !!0 .this::Get(!!0) + stloc b + + ldc.i4 0 + call !!0 .this::Get(!!0) + stloc offs + + ldloc a + + ldloca a + ldloc offs + add + ldloca b + sizeof int32 + cpblk + + ldc.i4 1 + bne.un FAILURE + ldc.i4 FALSE + ret + + FAILURE: + ldc.i4 TRUE + ret + } + + .method private static bool ProblemWithInitBlk() noinlining + { + .locals (int32 a, int32 b, int32 offs) + + ldc.i4 1 + call !!0 .this::Get(!!0) + stloc a + + ldc.i4 2 + call !!0 .this::Get(!!0) + stloc b + + ldc.i4 0 + call !!0 .this::Get(!!0) + stloc offs + + ldloc a + + ldloca a + ldloc offs + add + ldloc b + sizeof int32 + initblk + + ldc.i4 1 + bne.un FAILURE + ldc.i4 FALSE + ret + + FAILURE: + ldc.i4 TRUE + ret + } + + .method private static bool ProblemWithInitObj() noinlining + { + .locals (object a, int32 offs) + + newobj instance void object::.ctor() + call !!0 .this::Get(!!0) + stloc a + + ldc.i4 0 + call !!0 .this::Get(!!0) + stloc offs + + ldloc a + + ldloca a + ldloc offs + add + initobj object + + ldnull + beq FAILURE + ldc.i4 FALSE + ret + + FAILURE: + ldc.i4 TRUE + ret + } + + .method private static bool ProblemWithDirectInitObj() noinlining + { + .locals (valuetype StructWithInt a, valuetype StructWithInt* pA) + + ldloca a + ldflda int32 StructWithInt::Value + ldc.i4 1 + call !!0 .this::Get(!!0) + stfld int32 StructWithInt::Value + + ldloca a + stloc pA + + ldloc pA + ldfld int32 StructWithInt::Value + + ldloca a + initobj StructWithInt + + ldc.i4 1 + bne.un FAILURE + ldc.i4 FALSE + ret + + FAILURE: + ldc.i4 TRUE + ret + } + + .method private static bool ProblemWithRetBufCall() noinlining + { + .locals (valuetype LargeStruct a, valuetype LargeStruct b) + + ldc.i8 1 + call valuetype LargeStruct LargeStruct::CreateNoinline(int64) + dup + stloc a + stloc b + + ldloc a + + ldc.i8 2 + call valuetype LargeStruct LargeStruct::CreateInline(int64) + stloc a + + ldloc b + call bool LargeStruct::Equals(valuetype LargeStruct, valuetype LargeStruct) + brfalse FAILURE + + ldloc b + stloc a + + ldloc a + + ldc.i8 3 + call valuetype LargeStruct LargeStruct::CreateInline(int64) + stloc a + + ldloc b + call bool LargeStruct::Equals(valuetype LargeStruct, valuetype LargeStruct) + brfalse FAILURE + + ldc.i4 FALSE + ret + + FAILURE: + ldc.i4 TRUE + ret + } + + .method private static !!T Get(!!T arg) noinlining + { + ldarg arg + ret + } + + .method private static void ReportFailure(int32* pResult, string msg) noinlining + { + ldarg pResult + ldarg pResult + ldind.i4 + ldc.i4 1 + add + stind.i4 + + ldarg msg + call void [System.Console]System.Console::WriteLine(string) + + ret + } +} + +.class sealed sequential StructWithInt extends [System.Runtime]System.ValueType +{ + .field public int32 Value +} + +.class sealed sequential LargeStruct extends [System.Runtime]System.ValueType +{ + .field public int64 LongOne + .field public int64 LongTwo + .field public int64 LongThree + + .method public static bool Equals(valuetype LargeStruct left, valuetype LargeStruct right) noinlining + { + ldarg left + ldfld int64 .this::LongOne + ldarg right + ldfld int64 .this::LongOne + bne.un NOT_EQUAL + + ldarg left + ldfld int64 .this::LongTwo + ldarg right + ldfld int64 .this::LongTwo + bne.un NOT_EQUAL + + ldarg left + ldfld int64 .this::LongThree + ldarg right + ldfld int64 .this::LongThree + bne.un NOT_EQUAL + + ldc.i4 TRUE + ret + + NOT_EQUAL: + ldc.i4 FALSE + ret + } + + .method public static valuetype LargeStruct CreateInline(int64 fieldValue) + { + .locals (valuetype LargeStruct result) + + ldloca result + ldarg fieldValue + stfld int64 .this::LongOne + + ldloca result + ldarg fieldValue + stfld int64 .this::LongTwo + + ldloca result + ldarg fieldValue + stfld int64 .this::LongThree + + ldloc result + ret + } + + .method public static valuetype LargeStruct CreateNoinline(int64 fieldValue) noinlining + { + ldarg fieldValue + call valuetype LargeStruct .this::CreateInline(int64) + ret + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_72133/Runtime_72133.ilproj b/src/tests/JIT/Regression/JitBlue/Runtime_72133/Runtime_72133.ilproj new file mode 100644 index 0000000..c61c0c5 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_72133/Runtime_72133.ilproj @@ -0,0 +1,11 @@ + + + Exe + + + True + + + + +