unwind.cpp
utils.cpp
valuenum.cpp
+ stacklevelsetter.cpp
)
# Add header files to Visual Studio vcxproj, not required for unixes
genReserveEpilog(block);
}
-/*****************************************************************************
- *
- * Generate code for an out-of-line exception.
- * For debuggable code, we generate the 'throw' inline.
- * For non-dbg code, we share the helper blocks created by fgAddCodeRef().
- */
-
+//------------------------------------------------------------------------
+// genJumpToThrowHlpBlk: Generate code for an out-of-line exception.
+//
+// Notes:
+// For code that uses throw helper blocks, we share the helper blocks created by fgAddCodeRef().
+// Otherwise, we generate the 'throw' inline.
+//
+// Arguments:
+// jumpKind - jump kind to generate;
+// codeKind - the special throw-helper kind;
+// failBlk - optional fail target block, if it is already known;
+//
void CodeGen::genJumpToThrowHlpBlk(emitJumpKind jumpKind, SpecialCodeKind codeKind, GenTree* failBlk)
{
- bool useThrowHlpBlk = !compiler->opts.compDbgCode;
-
+ bool useThrowHlpBlk = compiler->fgUseThrowHelperBlocks();
#if defined(UNIX_X86_ABI) && FEATURE_EH_FUNCLETS
// Inline exception-throwing code in funclet to make it possible to unwind funclet frames.
useThrowHlpBlk = useThrowHlpBlk && (compiler->funCurrentFunc()->funKind == FUNC_ROOT);
if (useThrowHlpBlk)
{
- /* For non-debuggable code, find and use the helper block for
- raising the exception. The block may be shared by other trees too. */
+ // For code with throw helper blocks, find and use the helper block for
+ // raising the exception. The block may be shared by other trees too.
- BasicBlock* tgtBlk;
+ BasicBlock* excpRaisingBlock;
- if (failBlk)
+ if (failBlk != nullptr)
{
- /* We already know which block to jump to. Use that. */
+ // We already know which block to jump to. Use that.
+ assert(failBlk->gtOper == GT_LABEL);
+ excpRaisingBlock = failBlk->gtLabel.gtLabBB;
- noway_assert(failBlk->gtOper == GT_LABEL);
- tgtBlk = failBlk->gtLabel.gtLabBB;
- noway_assert(
- tgtBlk ==
- compiler->fgFindExcptnTarget(codeKind, compiler->bbThrowIndex(compiler->compCurBB))->acdDstBlk);
+#ifdef DEBUG
+ Compiler::AddCodeDsc* add =
+ compiler->fgFindExcptnTarget(codeKind, compiler->bbThrowIndex(compiler->compCurBB));
+ assert(excpRaisingBlock == add->acdDstBlk);
+#if !FEATURE_FIXED_OUT_ARGS
+ assert(add->acdStkLvlInit || isFramePointerUsed());
+#endif // !FEATURE_FIXED_OUT_ARGS
+#endif // DEBUG
}
else
{
- /* Find the helper-block which raises the exception. */
-
+ // Find the helper-block which raises the exception.
Compiler::AddCodeDsc* add =
compiler->fgFindExcptnTarget(codeKind, compiler->bbThrowIndex(compiler->compCurBB));
PREFIX_ASSUME_MSG((add != nullptr), ("ERROR: failed to find exception throw block"));
- tgtBlk = add->acdDstBlk;
+ excpRaisingBlock = add->acdDstBlk;
+#if !FEATURE_FIXED_OUT_ARGS
+ assert(add->acdStkLvlInit || isFramePointerUsed());
+#endif // !FEATURE_FIXED_OUT_ARGS
}
- noway_assert(tgtBlk);
-
- // Jump to the excption-throwing block on error.
+ noway_assert(excpRaisingBlock != nullptr);
- inst_JMP(jumpKind, tgtBlk);
+ // Jump to the exception-throwing block on error.
+ inst_JMP(jumpKind, excpRaisingBlock);
}
else
{
- /* The code to throw the exception will be generated inline, and
- we will jump around it in the normal non-exception case */
+ // The code to throw the exception will be generated inline, and
+ // we will jump around it in the normal non-exception case.
BasicBlock* tgtBlk = nullptr;
emitJumpKind reverseJumpKind = emitter::emitReverseJumpKind(jumpKind);
genEmitHelperCall(compiler->acdHelper(codeKind), 0, EA_UNKNOWN);
- /* Define the spot for the normal non-exception case to jump to */
+ // Define the spot for the normal non-exception case to jump to.
if (tgtBlk != nullptr)
{
assert(reverseJumpKind != jumpKind);
{
return m_cgFramePointerRequired;
}
+
void setFramePointerRequired(bool value)
{
m_cgFramePointerRequired = value;
}
+
+ //------------------------------------------------------------------------
+ // resetWritePhaseForFramePointerRequired: Return m_cgFramePointerRequired into the write phase.
+ // It is used only before the first phase, that locks this value, currently it is LSRA.
+ // Use it if you want to skip checks that set this value to true if the value is already true.
+ void resetWritePhaseForFramePointerRequired()
+ {
+ m_cgFramePointerRequired.ResetWritePhase();
+ }
+
void setFramePointerRequiredEH(bool value);
void setFramePointerRequiredGCInfo(bool value)
#ifndef LEGACY_BACKEND
#include "lower.h"
+#include "stacklevelsetter.h"
#endif // !LEGACY_BACKEND
#include "jittelemetry.h"
m_pLowering = new (this, CMK_LSRA) Lowering(this, m_pLinearScan); // PHASE_LOWERING
m_pLowering->Run();
+ StackLevelSetter stackLevelSetter(this); // PHASE_STACK_LEVEL_SETTER
+ stackLevelSetter.Run();
+
assert(lvaSortAgain == false); // We should have re-run fgLocalVarLiveness() in lower.Run()
lvaTrackedFixed = true; // We can not add any new tracked variables after this point.
unsigned fgPtrArgCntCur;
unsigned fgPtrArgCntMax;
- hashBv* fgOutgoingArgTemps;
- hashBv* fgCurrentlyInUseArgTemps;
+
+public:
+ //------------------------------------------------------------------------
+ // fgGetPtrArgCntMax: Return the maximum number of pointer-sized stack arguments that calls inside this method
+ // can push on the stack. This value is calculated during morph.
+ //
+ // Return Value:
+ // Returns fgPtrArgCntMax, that is a private field.
+ //
+ unsigned fgGetPtrArgCntMax() const
+ {
+ return fgPtrArgCntMax;
+ }
+
+ //------------------------------------------------------------------------
+ // fgSetPtrArgCntMax: Set the maximum number of pointer-sized stack arguments that calls inside this method
+ // can push on the stack. This function is used during StackLevelSetter to fix incorrect morph calculations.
+ //
+ void fgSetPtrArgCntMax(unsigned argCntMax)
+ {
+ fgPtrArgCntMax = argCntMax;
+ }
+
+private:
+ hashBv* fgOutgoingArgTemps;
+ hashBv* fgCurrentlyInUseArgTemps;
bool compCanEncodePtrArgCntMax();
BasicBlock* acdDstBlk; // block to which we jump
unsigned acdData;
SpecialCodeKind acdKind; // what kind of a special block is this?
- unsigned short acdStkLvl;
+#if !FEATURE_FIXED_OUT_ARGS
+ bool acdStkLvlInit; // has acdStkLvl value been already set?
+ unsigned acdStkLvl;
+#endif // !FEATURE_FIXED_OUT_ARGS
};
private:
public:
AddCodeDsc* fgFindExcptnTarget(SpecialCodeKind kind, unsigned refData);
+ bool fgUseThrowHelperBlocks();
+
+ AddCodeDsc* fgGetAdditionalCodeDescriptors()
+ {
+ return fgAddCodeList;
+ }
+
private:
bool fgIsCodeAdded();
CompPhaseNameMacro(PHASE_LOWERING_DECOMP, "Lowering decomposition", "LWR-DEC", false, -1, false)
CompPhaseNameMacro(PHASE_LOWERING, "Lowering nodeinfo", "LWR-INFO", false, -1, true)
#ifndef LEGACY_BACKEND
+CompPhaseNameMacro(PHASE_STACK_LEVEL_SETTER, "Calculate stack level slots", "STK-SET", false, -1, false)
CompPhaseNameMacro(PHASE_LINEAR_SCAN, "Linear scan register alloc", "LSRA", true, -1, true)
CompPhaseNameMacro(PHASE_LINEAR_SCAN_BUILD, "LSRA build intervals", "LSRA-BLD", false, PHASE_LINEAR_SCAN, false)
CompPhaseNameMacro(PHASE_LINEAR_SCAN_ALLOC, "LSRA allocate", "LSRA-ALL", false, PHASE_LINEAR_SCAN, false)
// arg slots on the stack frame if there are no other calls.
compUsesThrowHelper = true;
- // For debuggable code, genJumpToThrowHlpBlk() will generate the 'throw'
- // code inline. It has to be kept consistent with fgAddCodeRef()
- if (opts.compDbgCode)
+ if (!fgUseThrowHelperBlocks())
{
return nullptr;
}
if (add) // found it
{
-#ifdef _TARGET_X86_
+#if !FEATURE_FIXED_OUT_ARGS
// If different range checks happen at different stack levels,
// they can't all jump to the same "call @rngChkFailed" AND have
// frameless methods, as the rngChkFailed may need to unwind the
codeGen->setFramePointerRequiredGCInfo(true);
}
#endif // !defined(UNIX_X86_ABI)
-#endif // _TARGET_X86_
+#endif // !FEATURE_FIXED_OUT_ARGS
return add->acdDstBlk;
}
/* We have to allocate a new entry and prepend it to the list */
- add = new (this, CMK_Unknown) AddCodeDsc;
- add->acdData = refData;
- add->acdKind = kind;
- add->acdStkLvl = (unsigned short)stkDepth;
- noway_assert(add->acdStkLvl == stkDepth);
- add->acdNext = fgAddCodeList;
+ add = new (this, CMK_Unknown) AddCodeDsc;
+ add->acdData = refData;
+ add->acdKind = kind;
+ add->acdNext = fgAddCodeList;
+#if !FEATURE_FIXED_OUT_ARGS
+ add->acdStkLvl = stkDepth;
+ add->acdStkLvlInit = false;
+#endif // !FEATURE_FIXED_OUT_ARGS
+
fgAddCodeList = add;
/* Create the target basic block */
Compiler::AddCodeDsc* Compiler::fgFindExcptnTarget(SpecialCodeKind kind, unsigned refData)
{
- assert(!opts.compDbgCode);
+ assert(fgUseThrowHelperBlocks());
if (!(fgExcptnTargetCache[kind] && // Try the cached value first
fgExcptnTargetCache[kind]->acdData == refData))
{
assert(compIsForInlining());
return (lvaInlineeReturnSpillTemp != BAD_VAR_NUM);
}
+
+//------------------------------------------------------------------------
+// fgUseThrowHelperBlocks: Determinate does compiler use throw helper blocks.
+//
+// Note:
+// For debuggable code, codegen will generate the 'throw' code inline.
+// Return Value:
+// true if 'throw' helper block should be created.
+bool Compiler::fgUseThrowHelperBlocks()
+{
+ return !opts.compDbgCode;
+}
return gtOper == GT_PUTARG_STK;
}
+ bool OperIsPutArgStkOrSplit() const
+ {
+ return OperIsPutArgStk() || OperIsPutArgSplit();
+ }
+
bool OperIsPutArgReg() const
{
return gtOper == GT_PUTARG_REG;
GTSTRUCT_1(PhiArg , GT_PHI_ARG)
GTSTRUCT_1(StoreInd , GT_STOREIND)
GTSTRUCT_N(Indir , GT_STOREIND, GT_IND, GT_NULLCHECK, GT_BLK, GT_STORE_BLK, GT_OBJ, GT_STORE_OBJ, GT_DYN_BLK, GT_STORE_DYN_BLK)
+#if defined(LEGACY_BACKEND) || !defined(_TARGET_ARM_)
GTSTRUCT_1(PutArgStk , GT_PUTARG_STK)
-#if !defined(LEGACY_BACKEND) && defined(_TARGET_ARM_)
+#else // defined(_TARGET_ARM_) && !defined(LEGACY_BACKEND)
+GTSTRUCT_2_SPECIAL(PutArgStk, GT_PUTARG_STK, GT_PUTARG_SPLIT)
GTSTRUCT_1(PutArgSplit , GT_PUTARG_SPLIT)
-#endif
+#endif // defined(LEGACY_BACKEND) || !defined(_TARGET_ARM_)
GTSTRUCT_1(PhysReg , GT_PHYSREG)
#ifdef FEATURE_SIMD
GTSTRUCT_1(SIMD , GT_SIMD)
<CppCompile Include="..\hostallocator.cpp" />
<CppCompile Include="..\objectalloc.cpp" />
<CppCompile Include="..\sideeffects.cpp" />
+ <CppCompile Include="..\stacklevelsetter.cpp" />
<CppCompile Condition="'$(ClDefines.Contains(`LEGACY_BACKEND`))'=='True'" Include="..\CodeGenLegacy.cpp" />
<CppCompile Condition="'$(ClDefines.Contains(`LEGACY_BACKEND`))'=='False'" Include="..\Lower.cpp" />
<CppCompile Condition="'$(ClDefines.Contains(`LEGACY_BACKEND`))'=='False'" Include="..\LSRA.cpp" />
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "jitpch.h"
+#ifdef _MSC_VER
+#pragma hdrstop
+#endif
+
+#ifndef LEGACY_BACKEND // This file is ONLY used for the RyuJIT backend that uses the linear scan register allocator
+
+#include "stacklevelsetter.h"
+
+StackLevelSetter::StackLevelSetter(Compiler* compiler)
+ : Phase(compiler, "StackLevelSetter", PHASE_STACK_LEVEL_SETTER)
+ , currentStackLevel(0)
+ , maxStackLevel(0)
+ , memAllocator(compiler, CMK_fgArgInfoPtrArr)
+ , putArgNumSlots(&memAllocator)
+#if !FEATURE_FIXED_OUT_ARGS
+ , framePointerRequired(compiler->codeGen->isFramePointerRequired())
+ , throwHelperBlocksUsed(comp->fgUseThrowHelperBlocks() && comp->compUsesThrowHelper)
+#endif // !FEATURE_FIXED_OUT_ARGS
+{
+}
+
+//------------------------------------------------------------------------
+// DoPhase: Calculate stack slots numbers for outgoing args.
+//
+// Notes:
+// For non-x86 platforms it calculates the max number of slots
+// that calls inside this method can push on the stack.
+// This value is used for sanity checks in the emitter.
+//
+// Stack slots are pointer-sized: 4 bytes for 32-bit platforms, 8 bytes for 64-bit platforms.
+//
+// For x86 it also sets throw-helper blocks incoming stack depth and set
+// framePointerRequired when it is necessary. These values are used to pop
+// pushed args when an exception occurs.
+void StackLevelSetter::DoPhase()
+{
+ for (BasicBlock* block = comp->fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ ProcessBlock(block);
+ }
+#if !FEATURE_FIXED_OUT_ARGS
+
+ if (framePointerRequired && !comp->codeGen->isFramePointerRequired())
+ {
+ JITDUMP("framePointerRequired is not set when it is required\n");
+ comp->codeGen->resetWritePhaseForFramePointerRequired();
+ comp->codeGen->setFramePointerRequired(true);
+ }
+#endif // !FEATURE_FIXED_OUT_ARGS
+ assert(maxStackLevel <= comp->fgGetPtrArgCntMax());
+ if (maxStackLevel != comp->fgGetPtrArgCntMax())
+ {
+ JITDUMP("fgPtrArgCntMax was calculated wrong during the morph, the old value: %u, the right value: %u.\n",
+ comp->fgGetPtrArgCntMax(), maxStackLevel);
+ comp->fgSetPtrArgCntMax(maxStackLevel);
+ }
+}
+
+//------------------------------------------------------------------------
+// ProcessBlock: Do stack level calculations for one block.
+//
+// Notes:
+// Block starts and ends with an empty outgoing stack.
+// Nodes in blocks are iterated in the reverse order to memorize GT_PUTARG_STK
+// and GT_PUTARG_SPLIT stack sizes.
+//
+// Arguments:
+// block - the block to process.
+//
+void StackLevelSetter::ProcessBlock(BasicBlock* block)
+{
+ assert(currentStackLevel == 0);
+ LIR::ReadOnlyRange& range = LIR::AsRange(block);
+ for (auto i = range.rbegin(); i != range.rend(); ++i)
+ {
+ GenTree* node = *i;
+ if (node->OperIsPutArgStkOrSplit())
+ {
+ GenTreePutArgStk* putArg = node->AsPutArgStk();
+ unsigned numSlots = putArgNumSlots[putArg];
+ putArgNumSlots.Remove(putArg);
+ SubStackLevel(numSlots);
+ }
+
+#if !FEATURE_FIXED_OUT_ARGS
+ // Set throw blocks incoming stack depth for x86.
+ if (throwHelperBlocksUsed && !framePointerRequired)
+ {
+ bool operMightThrow = ((node->gtFlags & GTF_EXCEPT) != 0);
+ if (operMightThrow)
+ {
+ SetThrowHelperBlocks(node, block);
+ }
+ }
+#endif // !FEATURE_FIXED_OUT_ARGS
+
+ if (node->IsCall())
+ {
+ GenTreeCall* call = node->AsCall();
+
+ unsigned usedStackSlotsCount = PopArgumentsFromCall(call);
+#if defined(UNIX_X86_ABI)
+ assert(call->fgArgInfo->GetStkSizeBytes() == usedStackSlotsCount * TARGET_POINTER_SIZE);
+ call->fgArgInfo->SetStkSizeBytes(usedStackSlotsCount * TARGET_POINTER_SIZE);
+#endif // UNIX_X86_ABI
+ }
+ }
+ assert(currentStackLevel == 0);
+}
+
+#if !FEATURE_FIXED_OUT_ARGS
+//------------------------------------------------------------------------
+// SetThrowHelperBlocks: Set throw helper blocks incoming stack levels targeted
+// from the node.
+//
+// Notes:
+// one node can target several helper blocks.
+//
+// Arguments:
+// node - the node to process;
+// block - the source block for the node.
+void StackLevelSetter::SetThrowHelperBlocks(GenTree* node, BasicBlock* block)
+{
+ // Check that it uses throw block, find its kind, find the block, set level.
+ switch (node->OperGet())
+ {
+ case GT_ARR_BOUNDS_CHECK:
+#ifdef FEATURE_SIMD
+ case GT_SIMD_CHK:
+#endif // FEATURE_SIMD
+ {
+ GenTreeBoundsChk* bndsChk = node->AsBoundsChk();
+ SetThrowHelperBlock(bndsChk->gtThrowKind, block);
+ }
+ break;
+ case GT_INDEX_ADDR:
+ case GT_ARR_ELEM:
+ case GT_ARR_INDEX:
+ {
+ SetThrowHelperBlock(SCK_RNGCHK_FAIL, block);
+ }
+ break;
+
+ case GT_CKFINITE:
+ {
+ SetThrowHelperBlock(SCK_ARITH_EXCPN, block);
+ }
+ break;
+ }
+ if (node->gtOverflowEx())
+ {
+ SetThrowHelperBlock(SCK_OVERFLOW, block);
+ }
+}
+
+//------------------------------------------------------------------------
+// SetThrowHelperBlock: Set throw helper block incoming stack levels targeted
+// from the block with this kind.
+//
+// Notes:
+// Set framePointerRequired if finds that the block has several incoming edges
+// with different stack levels.
+//
+// Arguments:
+// kind - the special throw-helper kind;
+// block - the source block that targets helper.
+void StackLevelSetter::SetThrowHelperBlock(SpecialCodeKind kind, BasicBlock* block)
+{
+ Compiler::AddCodeDsc* add = comp->fgFindExcptnTarget(kind, comp->bbThrowIndex(block));
+ assert(add != nullptr);
+ if (add->acdStkLvlInit)
+ {
+ if (add->acdStkLvl != currentStackLevel)
+ {
+ framePointerRequired = true;
+ }
+ }
+ else
+ {
+ add->acdStkLvlInit = true;
+ if (add->acdStkLvl != currentStackLevel)
+ {
+ JITDUMP("Wrong stack level was set for block %d\n", add->acdDstBlk->bbNum);
+ }
+#ifdef DEBUG
+ add->acdDstBlk->bbTgtStkDepth = currentStackLevel;
+#endif // Debug
+ add->acdStkLvl = currentStackLevel;
+ }
+}
+
+#endif // !FEATURE_FIXED_OUT_ARGS
+
+//------------------------------------------------------------------------
+// PopArgumentsFromCall: Calculate the number of stack arguments that are used by the call.
+//
+// Notes:
+// memorize number of slots that each stack argument use.
+//
+// Arguments:
+// call - the call to process.
+//
+// Return value:
+// the number of stack slots in stack arguments for the call.
+unsigned StackLevelSetter::PopArgumentsFromCall(GenTreeCall* call)
+{
+ unsigned usedStackSlotsCount = 0;
+ fgArgInfo* argInfo = call->fgArgInfo;
+ if (argInfo->HasStackArgs())
+ {
+ for (unsigned i = 0; i < argInfo->ArgCount(); ++i)
+ {
+ fgArgTabEntry* argTab = argInfo->ArgTable()[i];
+ if (argTab->numSlots != 0)
+ {
+ GenTree* node = argTab->node;
+ assert(node->OperIsPutArgStkOrSplit());
+
+ GenTreePutArgStk* putArg = node->AsPutArgStk();
+
+#if !FEATURE_FIXED_OUT_ARGS
+ assert(argTab->numSlots == putArg->gtNumSlots);
+#endif // !FEATURE_FIXED_OUT_ARGS
+
+ putArgNumSlots.Set(putArg, argTab->numSlots);
+
+ usedStackSlotsCount += argTab->numSlots;
+ AddStackLevel(argTab->numSlots);
+ }
+ }
+ }
+ return usedStackSlotsCount;
+}
+
+//------------------------------------------------------------------------
+// SubStackLevel: Reflect pushing to the stack.
+//
+// Arguments:
+// value - a positive value to add.
+//
+void StackLevelSetter::AddStackLevel(unsigned value)
+{
+ currentStackLevel += value;
+
+ if (currentStackLevel > maxStackLevel)
+ {
+ maxStackLevel = currentStackLevel;
+ }
+}
+
+//------------------------------------------------------------------------
+// SubStackLevel: Reflect popping from the stack.
+//
+// Arguments:
+// value - a positive value to subtract.
+//
+void StackLevelSetter::SubStackLevel(unsigned value)
+{
+ assert(currentStackLevel >= value);
+ currentStackLevel -= value;
+}
+
+#endif // !LEGACY_BACKEND
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#pragma once
+
+#include "compiler.h"
+#include "phase.h"
+
+class StackLevelSetter : public Phase
+{
+public:
+ StackLevelSetter(Compiler* compiler);
+
+ virtual void DoPhase() override;
+
+private:
+ void ProcessBlock(BasicBlock* block);
+
+#if !FEATURE_FIXED_OUT_ARGS
+ void SetThrowHelperBlocks(GenTree* node, BasicBlock* block);
+ void SetThrowHelperBlock(SpecialCodeKind kind, BasicBlock* block);
+#endif // !FEATURE_FIXED_OUT_ARGS
+
+ unsigned PopArgumentsFromCall(GenTreeCall* call);
+ void AddStackLevel(unsigned value);
+ void SubStackLevel(unsigned value);
+
+private:
+ unsigned currentStackLevel; // current number of stack slots used by arguments.
+ unsigned maxStackLevel; // max number of stack slots for arguments.
+
+ CompAllocator memAllocator;
+
+ typedef JitHashTable<GenTreePutArgStk*, JitPtrKeyFuncs<GenTreePutArgStk>, unsigned> PutArgNumSlotsMap;
+ PutArgNumSlotsMap putArgNumSlots; // The hash table keeps stack slot sizes for active GT_PUTARG_STK nodes.
+
+#if !FEATURE_FIXED_OUT_ARGS
+ bool framePointerRequired; // Is frame pointer required based on the analysis made by this phase.
+ bool throwHelperBlocksUsed; // Were any throw helper blocks created for this method.
+#endif // !FEATURE_FIXED_OUT_ARGS
+};
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+.assembly extern System.Runtime {auto}
+.assembly extern System.Console {auto}
+.assembly DevDiv_534476 {}
+
+// The test showed a problem with stack level calculations during morph phase
+// that did not reflect code movements in the later phases.
+
+// In this case a helper call, created for IL_002c, will create a put_arg, but it will be moved from IL_0026 to IL_0014.
+// This put_arg movement will affect throw edge at IL_0021, that will have incorrect stack level.
+
+.class private auto ansi beforefieldinit DevDiv_534476.ILGEN_CLASS
+ extends [System.Runtime]System.Object
+{
+ .method private static float32
+ ILGEN_METHOD(uint64 e) cil managed
+ {
+ .maxstack 46
+ .locals init (native unsigned int, bool)
+ IL_0000: ldarg 0x0000
+ ldc.i4.1
+ IL_0012: conv.ovf.u2.un
+ IL_0014: shr.un
+ IL_001b: ldarg 0x0000
+ IL_0021: conv.ovf.u4.un // the first throw block edge.
+ IL_0023: ldloc.s 0x01
+ IL_0025: mul // mul value is known to be null.
+ IL_0026: shl // long decomposition will replace IL_0026 with IL_0014.
+ IL_002c: conv.r4 // will be transformed into helper call, with put_arg before IL_0021.
+ IL_0009: ckfinite // to create the second throw edge.
+
+ IL_002d: ret
+ } // end of method ILGEN_CLASS::ILGEN_METHOD
+
+.method private hidebysig static int32 Main(string[] args) cil managed
+{
+ .entrypoint
+ // Code size 30 (0x1e)
+ .maxstack 6
+ .locals init (int32 V_0)
+ IL_0000: nop
+ IL_000d: ldc.i4.1
+ IL_000e: conv.i8
+ IL_0011: call float32 DevDiv_534476.ILGEN_CLASS::ILGEN_METHOD(uint64)
+ IL_0016: pop
+ IL_0017: ldc.i4.s 100
+ IL_0019: stloc.0
+ IL_001a: br.s IL_001c
+ IL_001c: ldloc.0
+ IL_001d: ret
+} // end of method ILGEN_CLASS::Main
+
+
+} // end of class DevDiv_534476.ILGEN_CLASS
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <AssemblyName>$(MSBuildProjectName)</AssemblyName>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{95DFC527-4DC1-495E-97D7-E94EE1F7140D}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+ </PropertyGroup>
+ <!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "></PropertyGroup>
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <PropertyGroup>
+ <DebugType>None</DebugType>
+ <Optimize>True</Optimize>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="DevDiv_534476.il" />
+ </ItemGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+ <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project>