1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4
5#include "jitpch.h"
6#ifdef _MSC_VER
7#pragma hdrstop
8#endif
9
10#include "stacklevelsetter.h"
11
12StackLevelSetter::StackLevelSetter(Compiler* compiler)
13 : Phase(compiler, "StackLevelSetter", PHASE_STACK_LEVEL_SETTER)
14 , currentStackLevel(0)
15 , maxStackLevel(0)
16 , memAllocator(compiler->getAllocator(CMK_fgArgInfoPtrArr))
17 , putArgNumSlots(memAllocator)
18#if !FEATURE_FIXED_OUT_ARGS
19 , framePointerRequired(compiler->codeGen->isFramePointerRequired())
20 , throwHelperBlocksUsed(comp->fgUseThrowHelperBlocks() && comp->compUsesThrowHelper)
21#endif // !FEATURE_FIXED_OUT_ARGS
22{
23 // The constructor reads this value to skip iterations that could set it if it is already set.
24 compiler->codeGen->resetWritePhaseForFramePointerRequired();
25}
26
27//------------------------------------------------------------------------
28// DoPhase: Calculate stack slots numbers for outgoing args.
29//
30// Notes:
31// For non-x86 platforms it calculates the max number of slots
32// that calls inside this method can push on the stack.
33// This value is used for sanity checks in the emitter.
34//
35// Stack slots are pointer-sized: 4 bytes for 32-bit platforms, 8 bytes for 64-bit platforms.
36//
37// For x86 it also sets throw-helper blocks incoming stack depth and set
38// framePointerRequired when it is necessary. These values are used to pop
39// pushed args when an exception occurs.
40void StackLevelSetter::DoPhase()
41{
42 for (BasicBlock* block = comp->fgFirstBB; block != nullptr; block = block->bbNext)
43 {
44 ProcessBlock(block);
45 }
46#if !FEATURE_FIXED_OUT_ARGS
47
48 if (framePointerRequired)
49 {
50 comp->codeGen->setFramePointerRequired(true);
51 }
52#endif // !FEATURE_FIXED_OUT_ARGS
53
54 CheckAdditionalArgs();
55
56 comp->fgSetPtrArgCntMax(maxStackLevel);
57 CheckArgCnt();
58}
59
60//------------------------------------------------------------------------
61// ProcessBlock: Do stack level calculations for one block.
62//
63// Notes:
64// Block starts and ends with an empty outgoing stack.
65// Nodes in blocks are iterated in the reverse order to memorize GT_PUTARG_STK
66// and GT_PUTARG_SPLIT stack sizes.
67//
68// Arguments:
69// block - the block to process.
70//
71void StackLevelSetter::ProcessBlock(BasicBlock* block)
72{
73 assert(currentStackLevel == 0);
74 LIR::ReadOnlyRange& range = LIR::AsRange(block);
75 for (auto i = range.rbegin(); i != range.rend(); ++i)
76 {
77 GenTree* node = *i;
78 if (node->OperIsPutArgStkOrSplit())
79 {
80 GenTreePutArgStk* putArg = node->AsPutArgStk();
81 unsigned numSlots = putArgNumSlots[putArg];
82 putArgNumSlots.Remove(putArg);
83 SubStackLevel(numSlots);
84 }
85
86#if !FEATURE_FIXED_OUT_ARGS
87 // Set throw blocks incoming stack depth for x86.
88 if (throwHelperBlocksUsed && !framePointerRequired)
89 {
90 if (node->OperMayThrow(comp))
91 {
92 SetThrowHelperBlocks(node, block);
93 }
94 }
95#endif // !FEATURE_FIXED_OUT_ARGS
96
97 if (node->IsCall())
98 {
99 GenTreeCall* call = node->AsCall();
100 unsigned usedStackSlotsCount = PopArgumentsFromCall(call);
101#if defined(UNIX_X86_ABI)
102 call->fgArgInfo->SetStkSizeBytes(usedStackSlotsCount * TARGET_POINTER_SIZE);
103#endif // UNIX_X86_ABI
104 }
105 }
106 assert(currentStackLevel == 0);
107}
108
109#if !FEATURE_FIXED_OUT_ARGS
110//------------------------------------------------------------------------
111// SetThrowHelperBlocks: Set throw helper blocks incoming stack levels targeted
112// from the node.
113//
114// Notes:
115// one node can target several helper blocks, but not all operands that throw do this.
116// So the function can set 0-2 throw blocks depends on oper and overflow flag.
117//
118// Arguments:
119// node - the node to process;
120// block - the source block for the node.
121void StackLevelSetter::SetThrowHelperBlocks(GenTree* node, BasicBlock* block)
122{
123 assert(node->OperMayThrow(comp));
124
125 // Check that it uses throw block, find its kind, find the block, set level.
126 switch (node->OperGet())
127 {
128 case GT_ARR_BOUNDS_CHECK:
129#ifdef FEATURE_SIMD
130 case GT_SIMD_CHK:
131#endif // FEATURE_SIMD
132#ifdef FEATURE_HW_INTRINSICS
133 case GT_HW_INTRINSIC_CHK:
134#endif // FEATURE_HW_INTRINSICS
135 {
136 GenTreeBoundsChk* bndsChk = node->AsBoundsChk();
137 SetThrowHelperBlock(bndsChk->gtThrowKind, block);
138 }
139 break;
140 case GT_INDEX_ADDR:
141 case GT_ARR_ELEM:
142 case GT_ARR_INDEX:
143 {
144 SetThrowHelperBlock(SCK_RNGCHK_FAIL, block);
145 }
146 break;
147
148 case GT_CKFINITE:
149 {
150 SetThrowHelperBlock(SCK_ARITH_EXCPN, block);
151 }
152 break;
153 default: // Other opers can target throw only due to overflow.
154 break;
155 }
156 if (node->gtOverflowEx())
157 {
158 SetThrowHelperBlock(SCK_OVERFLOW, block);
159 }
160}
161
162//------------------------------------------------------------------------
163// SetThrowHelperBlock: Set throw helper block incoming stack levels targeted
164// from the block with this kind.
165//
166// Notes:
167// Set framePointerRequired if finds that the block has several incoming edges
168// with different stack levels.
169//
170// Arguments:
171// kind - the special throw-helper kind;
172// block - the source block that targets helper.
173void StackLevelSetter::SetThrowHelperBlock(SpecialCodeKind kind, BasicBlock* block)
174{
175 Compiler::AddCodeDsc* add = comp->fgFindExcptnTarget(kind, comp->bbThrowIndex(block));
176 assert(add != nullptr);
177 if (add->acdStkLvlInit)
178 {
179 // If different range checks happen at different stack levels,
180 // they can't all jump to the same "call @rngChkFailed" AND have
181 // frameless methods, as the rngChkFailed may need to unwind the
182 // stack, and we have to be able to report the stack level.
183 //
184 // The following check forces most methods that reference an
185 // array element in a parameter list to have an EBP frame,
186 // this restriction could be removed with more careful code
187 // generation for BBJ_THROW (i.e. range check failed).
188 //
189 // For Linux/x86, we possibly need to insert stack alignment adjustment
190 // before the first stack argument pushed for every call. But we
191 // don't know what the stack alignment adjustment will be when
192 // we morph a tree that calls fgAddCodeRef(), so the stack depth
193 // number will be incorrect. For now, simply force all functions with
194 // these helpers to have EBP frames. It might be possible to make
195 // this less conservative. E.g., for top-level (not nested) calls
196 // without stack args, the stack pointer hasn't changed and stack
197 // depth will be known to be zero. Or, figure out a way to update
198 // or generate all required helpers after all stack alignment
199 // has been added, and the stack level at each call to fgAddCodeRef()
200 // is known, or can be recalculated.
201 CLANG_FORMAT_COMMENT_ANCHOR;
202#if defined(UNIX_X86_ABI)
203 framePointerRequired = true;
204#else // !defined(UNIX_X86_ABI)
205 if (add->acdStkLvl != currentStackLevel)
206 {
207 framePointerRequired = true;
208 }
209#endif // !defined(UNIX_X86_ABI)
210 }
211 else
212 {
213 add->acdStkLvlInit = true;
214 if (add->acdStkLvl != currentStackLevel)
215 {
216 JITDUMP("Wrong stack level was set for " FMT_BB "\n", add->acdDstBlk->bbNum);
217 }
218#ifdef DEBUG
219 add->acdDstBlk->bbTgtStkDepth = currentStackLevel;
220#endif // Debug
221 add->acdStkLvl = currentStackLevel;
222 }
223}
224
225#endif // !FEATURE_FIXED_OUT_ARGS
226
227//------------------------------------------------------------------------
228// PopArgumentsFromCall: Calculate the number of stack arguments that are used by the call.
229//
230// Notes:
231// memorize number of slots that each stack argument use.
232//
233// Arguments:
234// call - the call to process.
235//
236// Return value:
237// the number of stack slots in stack arguments for the call.
238unsigned StackLevelSetter::PopArgumentsFromCall(GenTreeCall* call)
239{
240 unsigned usedStackSlotsCount = 0;
241 fgArgInfo* argInfo = call->fgArgInfo;
242 if (argInfo->HasStackArgs())
243 {
244 for (unsigned i = 0; i < argInfo->ArgCount(); ++i)
245 {
246 fgArgTabEntry* argTab = argInfo->ArgTable()[i];
247 if (argTab->numSlots != 0)
248 {
249 GenTree* node = argTab->node;
250 assert(node->OperIsPutArgStkOrSplit());
251
252 GenTreePutArgStk* putArg = node->AsPutArgStk();
253
254#if !FEATURE_FIXED_OUT_ARGS
255 assert(argTab->numSlots == putArg->gtNumSlots);
256#endif // !FEATURE_FIXED_OUT_ARGS
257
258 putArgNumSlots.Set(putArg, argTab->numSlots);
259
260 usedStackSlotsCount += argTab->numSlots;
261 AddStackLevel(argTab->numSlots);
262 }
263 }
264 }
265 return usedStackSlotsCount;
266}
267
268//------------------------------------------------------------------------
269// SubStackLevel: Reflect pushing to the stack.
270//
271// Arguments:
272// value - a positive value to add.
273//
274void StackLevelSetter::AddStackLevel(unsigned value)
275{
276 currentStackLevel += value;
277
278 if (currentStackLevel > maxStackLevel)
279 {
280 maxStackLevel = currentStackLevel;
281 }
282}
283
284//------------------------------------------------------------------------
285// SubStackLevel: Reflect popping from the stack.
286//
287// Arguments:
288// value - a positive value to subtract.
289//
290void StackLevelSetter::SubStackLevel(unsigned value)
291{
292 assert(currentStackLevel >= value);
293 currentStackLevel -= value;
294}
295
296//------------------------------------------------------------------------
297// CheckArgCnt: Check whether the maximum arg size will change codegen requirements.
298//
299// Notes:
300// CheckArgCnt records the maximum number of pushed arguments.
301// Depending upon this value of the maximum number of pushed arguments
302// we may need to use an EBP frame or be partially interuptible.
303// This functionality has to be called after maxStackLevel is set.
304//
305// Assumptions:
306// This must be called when isFramePointerRequired() is in a write phase, because it is a
307// phased variable (can only be written before it has been read).
308//
309void StackLevelSetter::CheckArgCnt()
310{
311 if (!comp->compCanEncodePtrArgCntMax())
312 {
313#ifdef DEBUG
314 if (comp->verbose)
315 {
316 printf("Too many pushed arguments for fully interruptible encoding, marking method as partially "
317 "interruptible\n");
318 }
319#endif
320 comp->genInterruptible = false;
321 }
322 if (maxStackLevel >= sizeof(unsigned))
323 {
324#ifdef DEBUG
325 if (comp->verbose)
326 {
327 printf("Too many pushed arguments for an ESP based encoding, forcing an EBP frame\n");
328 }
329#endif
330 comp->codeGen->setFramePointerRequired(true);
331 }
332}
333
334//------------------------------------------------------------------------
335// CheckAdditionalArgs: Check if there are additional args that need stack slots.
336//
337// Notes:
338// Currently only x86 profiler hook needs it.
339//
340void StackLevelSetter::CheckAdditionalArgs()
341{
342#if defined(_TARGET_X86_)
343 if (comp->compIsProfilerHookNeeded())
344 {
345 if (maxStackLevel == 0)
346 {
347 JITDUMP("Upping fgPtrArgCntMax from %d to 1\n", maxStackLevel);
348 maxStackLevel = 1;
349 }
350 }
351#endif // _TARGET_X86_
352}
353