-
Notifications
You must be signed in to change notification settings - Fork 64
Expand file tree
/
Copy pathemit.c
More file actions
349 lines (277 loc) · 11.6 KB
/
Copy pathemit.c
File metadata and controls
349 lines (277 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
#include "emit.h"
#include "lex.h"
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
/*
16-byte aligned
For example, when ALIGN is 4,
16 = 2 * 2 * 2 * 2 = pow(2, 4) = 2**4 (python style)
= (1 << 4)
we will have
STACK_SIZE_ALIGNED(6) ---> 16
STACK_SIZE_ALIGNED(16) ---> 16
STACK_SIZE_ALIGNED(18) ---> 32
STACK_SIZE_ALIGNED(20) ---> 32
Low-level bit operations in C, you can skip the following detail.
To keep it simple, let us assume an integer is only 8-bit long here.
For example, when s is 6,
1.
s = 00000110
2. (1 << ALIGN) - 1) when ALIGN is 4
(1 << ALIGN) - 1) = 15 = 00001111
3. ~(1 << ALIGN) - 1)
~(1 << ALIGN) - 1) = 11110000
4. ((1 << ALIGN) - 1) + (s)
00000110
+ 00001111
------------------
00010101
5. (((1 << ALIGN) - 1) + (s)) & (~((1 << ALIGN) - 1))
00010101
& 11110000
----------------
00010000 = 16
So, we have
STACK_SIZE_ALIGNED(6) ---> 16
*/
#define ALIGN 4
#define STACK_SIZE_ALIGNED(s) (((1 << ALIGN) - 1) + (s)) & (~((1 << ALIGN) - 1))
void EmitPrologue(AstFuncDefNodePtr func) {
/***********************************************************
Generating the following assembly code at the entry of a function:
Two physical registers in x86_64 are used to access the call stack.
See the stack layout in func.c for more about rbp.
rbp
the frame pointer
rsp
the stack pointer
Example:
pushq %rbp
movq %rsp, %rbp
subq $80, %rsp
----------------------------------------------------------------
The layout of a call stack when we are just entering a function.
The return address (an address in the code section) on the above call stack
is pushed implicitly by the call instruction,
such that when the callee returns, the control flow can go back to caller's code.
High
| | <----- caller's frame pointer rbp (active rbp)
| caller's |
| frame |
| |
|__ret_addr___| <----- rsp
| |
Low
Call Stack (The unseen but maybe the most important stack)
(1) pushq %rbp
Save the caller's rbp (frame pointer)
| | <----- caller's frame pointer rbp (active)
| caller's |
| frame |
| |
|__ret_addr___|
|caller's rbp | <----- rsp
| |
(2) movq %rsp, %rbp
Set callee's frame pointer by copying rsp to rbp
| | <----- caller's frame pointer rbp (inactive)
| caller's |
| frame |
| |
|__ret_addr___|
|caller's rbp | <----- rsp | callee's frame pointer rbp (active rbp)
| |
(3) subq $80, %rsp
Allocate enough stack space for local variables, temporary variables, ....
The x86_64 call stack grows from high to low.
So a subq instruction (subtraction) will allocate stack space for the callee.
High
| | <----- caller's frame pointer rbp (inactive)
| caller's |
| frame |
| |
|__ret_addr___|
|caller's rbp | <----- callee's frame pointer rbp (active rbp)
| |
| callee's |
| frame |
| |
| | <----- rsp
Low
-----------------------------------------------------------
************************************************************/
EmitComments("# Function Prologue");
EmitAssembly("pushq %%rbp");
EmitAssembly("movq %%rsp, %%rbp");
if (func->frameSize != 0) {
EmitComments("# Frame size = %d", func->frameSize);
EmitComments(
"# Number of Parameters/Locals/Temps = %d(%d registers), %d, %d",
func->para_cnt, NUM_OF_ARGS_IN_REGS, func->local_vars_cnt,
func->tmpVarNum);
// See the stack memory layout in func.c
int result =
(NUM_OF_ARGS_IN_REGS + func->local_vars_cnt + func->tmpVarNum) *
CPU_WORD_SIZE_IN_BYTES;
EmitComments("# Frame size = (%d + %d + %d) * %d = %d",
NUM_OF_ARGS_IN_REGS, func->local_vars_cnt, func->tmpVarNum,
CPU_WORD_SIZE_IN_BYTES, result);
//assert(func->frameSize == result);
EmitComments("# 16-byte-aligned frame size = %d",
STACK_SIZE_ALIGNED(func->frameSize));
EmitAssembly("subq $%d, %%rsp", STACK_SIZE_ALIGNED(func->frameSize));
}
EmitComments("\n");
}
void EmitEpilogue(void) {
/********************************
Generating the following assembly code at the exit of a function:
Example:
movq %rbp, %rsp
popq %rbp
ret
The stack layout before the function returns.
High
| | <----- caller's frame pointer rbp (inactive)
| caller's |
| frame |
| |
|__ret_addr___|
|caller's rbp | <----- callee's frame pointer rbp (active rbp)
| |
| callee's |
| frame |
| |
| | <----- rsp
Low
Call Stack
(1) movq %rbp, %rsp
Release the stack space allocated to callee, by copying %rbp to %rsp
High
| | <----- caller's frame pointer rbp (inactive)
| caller's |
| frame |
| |
|__ret_addr___|
|caller's rbp | <----- callee's frame pointer rbp (active rbp) | rsp
| |
| callee's |
| frame |
| |
| | <-----
Low
(2) popq %rbp
Restore the caller's frame pointer.
Implicitly, rsp is increased by 8.
Now, all the local variables in the callee function are released.
High
| | <----- caller's frame pointer rbp (active rbp)
| caller's |
| frame |
| |
|__ret_addr___| <----- rsp
|caller's rbp |
| |
| |
Low
(3) ret
The return value has been saved in the %rax register by the callee before it returns.
The ret instruction will pop the return address to the instruction pointer register %rip.
Then the control flow will go back to the caller function.
Again, rsp += 8
High
| | <----- caller's frame pointer rbp (active rbp)
| caller's |
| frame |
| | <----- rsp
|__ret_addr___|
|caller's rbp |
| |
| |
Low
-------------------------------------------------------------
Sidetrack:
C Example:
long f() {
...
return 9024;
}
void g() {
...
n = f(); // we are here in C
}
--------------------------------------------------------------
Assembly code in code section
g:
...
call f # we are here in assembly
ret_addr:
movq %rax, n # move the return value to n
....
---------------------------------
f:
pushq %rbp # Prologue
movq %rsp, %rbp
subq $80, %rsp
...
movq $9024, %rax
movq %rbp, %rsp # Epilogue
popq %rbp
ret
----------------------------------------------------------------
*********************************/
EmitComments("# Function Epilogue");
EmitAssembly("movq %%rbp, %%rsp");
EmitAssembly("popq %%rbp");
EmitAssembly("ret");
EmitComments("\n");
}
/*
void DoEmitAssembly(int indents, const char *fmt, ...);
DoEmitAssembly() is a variadic function in C.
It can be called with a variable number of arguments.
The number of arguments passed by its caller can be larger than
the number of named parameters defined in DoEmitAssembly(),
DoEmitAssembly() has only two named parameters (i.e., indents and fmt).
So you can imagine that there are some unnamed parameters in a variadic function.
To access these unnamed parameters, we can use the macros defined in <stdarg.h>.
(va_start, var_arg, ... ).
If you want to know it inside out,
start with 32-bit variadic functions (see COMP9024/Stacks/CallStack/OurPrintf32.c),
where all the arguments are pushed on the call stack.
*/
void DoEmitAssembly(int indents, const char *fmt, ...) {
#define WHILE_SPACE_STRING " "
SccIOFileInfo *fileInfo = GetSccIOFileInfo();
do {
/*
On a 64-bit system, va_list is defined as an array type as follows.
typedef struct __va_list_tag __builtin_va_list[1];
typedef __builtin_va_list va_list.
So when ap is passed to vfprintf(), only ap's address is passed.
We don't assume ap's content will be the same after vfprintf() returns.
So in the following if-statement, we create a new va_list object (ap2).
*/
va_list ap;
va_start(ap, fmt);
for (int i = 0; i < indents; i++) {
fprintf(fileInfo->sccAssemblyFile, WHILE_SPACE_STRING);
}
vfprintf(fileInfo->sccAssemblyFile, fmt, ap);
fprintf(fileInfo->sccAssemblyFile, "\n");
va_end(ap);
} while (0);
// Write output to stdout for debugging scc
if (fileInfo->sccAssemblyFile != stdout) {
va_list ap2;
va_start(ap2, fmt);
for (int i = 0; i < indents; i++) {
fprintf(stdout, WHILE_SPACE_STRING);
}
vfprintf(stdout, fmt, ap2);
fprintf(stdout, "\n");
va_end(ap2);
}
}