1 /**
2 Copyright: Copyright (c) 2018-2019 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 
7 ///
8 module vox.be.stack_layout;
9 
10 import std.stdio;
11 import std.string : format;
12 import vox.all;
13 
14 // Stack Allocation
15 // https://docs.microsoft.com/en-us/cpp/build/stack-allocation?view=vs-2017
16 
17 // Before prolog our layout is this
18 // ++             slot index
19 // ???? paramN    PN-1 rsp +  8 + N*8
20 // ???? ...
21 // XXZ0 param5    P5   rsp + 40
22 // XXY8 param4    S3   rsp + 32                     \
23 // XXY0 param3    S2   rsp + 24                      \ shadow space
24 // XXX8 param2    S1   rsp + 16                      /
25 // XXX0 param1    S0   rsp + 8                      / aligned to 16 bytes
26 // XXW8 ret addr       rsp + 0     <-- RSP
27 // --
28 
29 // Shadow space is prioritized for first 4 arguments
30 // After allocating local stack space
31 // ++             slot index
32 // ???? paramN    PN-1 rsp +  8 + N*8              N parameters
33 // ???? ...
34 // XXZ0 param5    P5   rsp + 72
35 // XXY8 shadow3   S3   rsp + 64                     \
36 // XXY0 shadow2   S2   rsp + 56                      \ shadow space
37 // XXX8 shadow1   S1   rsp + 48                      /
38 // XXX0 shadow0   S0   rsp + 40                     / aligned to 16 bytes
39 // XXW8 ret addr       rsp + 32
40 // XXW0 local0    L0   rsp + 24      \
41 // XXV8 local1    L1   rsp + 16       \ numLocals = 4 (example)
42 // XXV0 local2    L2   rsp +  8       /
43 // XXU8 local3    L3   rsp +  0      /  <-- RSP
44 // optional padding                     <-- RSP
45 // --
46 // all space after ret addr is of size 'lir.stackFrameSize'
47 
48 enum STACK_ITEM_SIZE = 8; // x86_64
49 
50 /// Arranges items on the stack according to calling convention
51 void pass_stack_layout(CompilationContext* context, FunctionDeclNode* func)
52 {
53 	if (func.isExternal) return;
54 
55 	auto lir = context.getAst!IrFunction(func.backendData.lirData);
56 
57 	CallConv* callConv = lir.getCallConv(context);
58 	IrIndex baseReg = IrIndex(callConv.stackPointer, ArgType.QWORD);
59 
60 	// 1, 2, 4, 8, 16
61 	uint[5] numAlignments;
62 	uint[5] alignmentSizes;
63 	uint[5] alignmentOffsets;
64 
65 	lir.stackFrameSize = 0;
66 
67 	foreach (i, ref StackSlot slot; lir.stackSlots)
68 	{
69 		if (slot.isParameter) continue;
70 
71 		uint alignPow = slot.sizealign.alignmentPower;
72 		context.assertf(alignPow <= 4, "Big alignments (> 16) aren't implemented");
73 
74 		++numAlignments[alignPow];
75 		alignmentSizes[alignPow] += slot.sizealign.size;
76 		lir.stackFrameSize += slot.sizealign.size;
77 	}
78 	//writefln("reservedBytes1 0x%X", lir.stackFrameSize);
79 
80 	if (context.useFramePointer)
81 	{
82 		// ++        varIndex
83 		// shadow4                                  \
84 		// shadow3                                   \ shadow space
85 		// param2    1              \                /
86 		// param1    0  rbp + 2     / numParams = 2 / this address is aligned to 16 bytes
87 		// ret addr     rbp + 1
88 		// rbp      <-- rbp + 0     frame pointer
89 		// saved r0
90 		// saved r1
91 		// opt pad
92 		// local1    2  rbp - 1     \
93 		// local2    3  rbp - 2     / numLocals = 2
94 		// --
95 		// baseReg = IrIndex(callConv.framePointer, ArgType.QWORD); // TODO: offset must be relative to frame pointer
96 		// frame pointer is stored together with locals
97 		lir.stackFrameSize += STACK_ITEM_SIZE;
98 	}
99 	//writefln("reservedBytes2 0x%X", lir.stackFrameSize);
100 
101 	alignmentOffsets[4] = 0;
102 	foreach_reverse (i; 0..4)
103 	{
104 		alignmentOffsets[i] = alignmentOffsets[i+1] + alignmentSizes[i+1];
105 	}
106 
107 	// align to 8 bytes first
108 	lir.stackFrameSize = alignValue(lir.stackFrameSize, STACK_ITEM_SIZE);
109 	//writefln("reservedBytes3 0x%X", lir.stackFrameSize);
110 
111 	if (lir.numCalls != 0) {
112 		// Align to 16 bytes when we have calls to other functions
113 		// Before we are called, the stack is aligned to 16 bytes, after call return address is pushed
114 		// We take into account the return address (extra 8 bytes)
115 		if ((lir.stackFrameSize + STACK_ITEM_SIZE) % 16 != 0) {
116 			lir.stackFrameSize += STACK_ITEM_SIZE;
117 		}
118 	}
119 	//writefln("reservedBytes4 0x%X", lir.stackFrameSize);
120 
121 	uint paramsOffset = lir.stackFrameSize + STACK_ITEM_SIZE; // locals size + ret addr
122 	if (callConv.hasShadowSpace) paramsOffset += 32;
123 
124 	// TODO utilize shadow space
125 	// TODO utilize red zone
126 
127 	int nextLocalIndex = 0;
128 	foreach (i, ref StackSlot slot; lir.stackSlots)
129 	{
130 		if (slot.isParameter)
131 		{
132 			// ABI lowering code inserts correct offset from the start of stack arguments
133 			slot.displacement = paramsOffset + slot.displacement;
134 		}
135 		else
136 		{
137 			uint index = slot.sizealign.alignmentPower;
138 			alignmentSizes[index] -= slot.sizealign.size;
139 			slot.displacement = alignmentOffsets[index] + alignmentSizes[index];
140 		}
141 		slot.baseReg = baseReg;
142 	}
143 
144 	if (context.printStackLayout) lir.dumpStackSlots(context);
145 }