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
|
#include <assert.h>
#include <stdlib.h>
#include <qbt/regalloc.h>
#include <qbt/vec.h>
#include <qbt/abi.h>
/* first try to use temporaries, then callee-save, then finally args.
* Idea is that callee-save cost is paid once at the start of the function call,
* after that they're free. Argument registers are generally important to keep
* free to make sure function calls in loops etc. don't have to shuffle
* registers around as much */
static const int64_t tr_map[] = {
RT0, RT1, RT2, RT3, RT4, RT5, RT6, RT7, RT8, RT9,
RT10, RT11, RT12, RT13, RT14, RT15, RT16, RT17, RT18, RT19,
RT20, RT21, RT22, RT23,
RS0, RS1, RS2, RS3, RS4, RS5, RS6, RS7, RS8, RS9,
RS10, RS11, RS12, RS13, RS14, RS15, RS16, RS17, RS18, RS19,
RS20, RS21, RS22, RS23,
RA0, RA1, RA2, RA3, RA4, RA5, RA6, RA7, RA8, RA9,
RA10, RA11, RA12, RA13, RA14, RA15, RA16, RA17, RA18, RA19,
RA20, RA21, RA22, RA23,
};
#define reg_at(v, i) \
vect_at(struct val, v, i)
static bool has_rewrite_rule(struct vec *rmap, struct val t)
{
if (t.r >= (int64_t)vec_len(rmap))
return false;
struct val r = reg_at(*rmap, t.r);
return r.class != NOCLASS;
}
static struct val rewrite_tmp(struct vec *rmap, struct val t)
{
assert(t.class == TMP);
assert(t.r < (int64_t)vec_len(rmap));
struct val r = reg_at(*rmap, t.r);
assert(r.class != NOCLASS);
return r;
}
static void add_rewrite_rule(struct vec *rmap, struct val from, struct val to)
{
assert(from.class == TMP);
assert(to.class == REG);
/* note <=, we go one 'beyond' just to make sure that 0 fits */
while ((int64_t)vec_len(rmap) <= from.r) {
struct val no = noclass();
vec_append(rmap, &no);
}
reg_at(*rmap, from.r) = to;
}
static void add_hint(struct vec *hints, struct val from, struct val to)
{
/* kind of a hack */
add_rewrite_rule(hints, from, to);
}
static struct val get_hint(struct vec *hints, struct val from)
{
if ((int64_t)vec_len(hints) <= from.r)
return noclass();
return rewrite_tmp(hints, from);
}
struct lifetime {
struct val v;
size_t start;
size_t end;
size_t used;
};
#define lifetime_at(lifetimes, i) \
vect_at(struct lifetime, lifetimes, i)
#define foreach_lifetime(iter, lifetimes) \
foreach_vec(iter, lifetimes)
static void add_def(struct vec *lifetimes, struct val v, size_t i)
{
while ((int64_t)vec_len(lifetimes) <= v.r) {
vec_append(lifetimes, &(struct lifetime){noclass(), 0, 0, 0});
}
/* I guess a def isn't techincally a 'use' but good enough, we assume
* that unused variables have already been removed by some earlier stage
* (not currently true but that's the intention) */
lifetime_at(*lifetimes, v.r) = (struct lifetime){v, i, 0, 1};
}
static void add_use(struct vec *lifetimes, struct val v, size_t i)
{
assert((int64_t)vec_len(lifetimes) > v.r);
struct lifetime l = lifetime_at(*lifetimes, v.r);
assert(l.used);
l.used++;
l.end = i;
lifetime_at(*lifetimes, v.r) = l;
}
static void collect_lifetimes(struct blk *b, struct vec *hints,
struct vec *lifetimes)
{
foreach_blk_param(pi, b->params) {
struct val v = blk_param_at(b->params, pi);
add_def(lifetimes, v, 0);
}
size_t pos = 1;
foreach_insn(ii, b->insns) {
struct insn i = insn_at(b->insns, ii);
if (i.in[0].class == TMP)
add_use(lifetimes, i.in[0], pos);
if (i.in[1].class == TMP)
add_use(lifetimes, i.in[1], pos);
if (i.out.class == TMP)
add_def(lifetimes, i.out, pos);
/* collect some early hints */
if (i.type == MOVE) {
/* input arguments, retvals */
if (i.out.class == TMP && i.in[0].class == REG)
add_hint(hints, i.out, i.in[0]);
if (i.out.class == REG && i.in[0].class == TMP)
add_hint(hints, i.in[0], i.out);
}
pos++;
}
if (b->cmp[0].class == TMP)
add_use(lifetimes, b->cmp[0], pos);
if (b->cmp[1].class == TMP)
add_use(lifetimes, b->cmp[1], pos);
foreach_blk_param(pi, b->args1) {
struct val v = blk_param_at(b->args1, pi);
add_use(lifetimes, v, pos);
}
foreach_blk_param(pi, b->args2) {
struct val v = blk_param_at(b->args2, pi);
add_use(lifetimes, v, pos);
}
}
static void build_active(struct vec *active, struct vec *lifetimes, size_t i)
{
struct lifetime ref = lifetime_at(*lifetimes, i);
foreach_lifetime(li, *lifetimes) {
struct lifetime l = lifetime_at(*lifetimes, li);
if (l.used == 0)
continue;
if (l.end < ref.start)
continue;
if (l.start > ref.end)
continue;
vec_append(active, &l);
}
}
static void build_reserved(struct vec *reserved, struct vec *active,
struct vec *rmap)
{
foreach_lifetime(li, *active) {
struct lifetime l = lifetime_at(*active, li);
if (l.used == 0)
continue;
if (has_rewrite_rule(rmap, l.v)) {
struct val act = rewrite_tmp(rmap, l.v);
vec_append(reserved, &act);
}
}
}
static bool reg_free(struct vec *reserved, struct val f)
{
assert(f.class == REG);
/* not the fastest way in the world, but good enough for now */
foreach_val(ri, *reserved) {
struct val r = val_at(*reserved, ri);
if (same_val(r, f))
return false;
}
return true;
}
static struct val find_free_reg(struct vec *reserved)
{
for (size_t i = 0; i < sizeof(tr_map) / sizeof(tr_map[0]); ++i) {
struct val r = reg_val(tr_map[i]);
if (reg_free(reserved, r))
return r;
}
/* handle spill case later */
assert(0 &&
"ran out of registers, time to implement proper spill handling");
abort();
}
static void build_rmap(struct vec *hints, struct vec *lifetimes,
struct vec *rmap)
{
struct vec active = vec_create(sizeof(struct lifetime));
struct vec reserved = vec_create(sizeof(struct val));
foreach_lifetime(li, *lifetimes) {
struct lifetime l = lifetime_at(*lifetimes, li);
if (l.used == 0)
continue;
vec_reset(&active);
build_active(&active, lifetimes, li);
build_reserved(&reserved, &active, rmap);
struct val h = get_hint(hints, l.v);
if (h.class != NOCLASS) {
if (reg_free(&reserved, h)) {
add_rewrite_rule(rmap, l.v, h);
continue;
}
}
/* eventually we should select the spill register only for the
* least used register, but that's a bit more complicated than
* just this linear scan */
struct val f = find_free_reg(&reserved);
add_rewrite_rule(rmap, l.v, f);
}
}
/* has_calls is kind of in an iffy place but I guess it's fine */
static void do_rewrites(struct blk *b, struct vec *rmap, struct fn *f)
{
foreach_blk_param(pi, b->params) {
struct val p = blk_param_at(b->params, pi);
blk_param_at(b->params, pi) = rewrite_tmp(rmap, p);
}
foreach_insn(ii, b->insns) {
struct insn i = insn_at(b->insns, ii);
if (i.in[0].class == TMP)
i.in[0] = rewrite_tmp(rmap, i.in[0]);
if (i.in[1].class == TMP)
i.in[1] = rewrite_tmp(rmap, i.in[1]);
if (i.out.class == TMP)
i.out = rewrite_tmp(rmap, i.out);
/** @todo save caller-save at callsites here */
if (i.type == CALL)
f->has_calls = true;
/* write back changes, christ I forget this a lot */
insn_at(b->insns, ii) = i;
}
if (b->cmp[0].class == TMP)
b->cmp[0] = rewrite_tmp(rmap, b->cmp[0]);
if (b->cmp[1].class == TMP)
b->cmp[1] = rewrite_tmp(rmap, b->cmp[1]);
foreach_blk_param(pi, b->args1) {
struct val p = blk_param_at(b->args1, pi);
blk_param_at(b->args1, pi) = rewrite_tmp(rmap, p);
}
foreach_blk_param(pi, b->args2) {
struct val p = blk_param_at(b->args2, pi);
blk_param_at(b->args2, pi) = rewrite_tmp(rmap, p);
}
}
/* assumes ssa form */
void regalloc(struct fn *f)
{
struct vec hints = vec_create(sizeof(struct val));
struct vec lifetimes = vec_create(sizeof(struct lifetime));
struct vec rmap = vec_create(sizeof(struct val));
/* here it would probably make sense to iterate through lifetimes in
* order of priority, like loop nesting/count etc */
foreach_blk(bi, f->blks) {
struct blk *b = blk_at(f->blks, bi);
vec_reset(&lifetimes);
/** @todo collect hints from args/params */
collect_lifetimes(b, &hints, &lifetimes);
build_rmap(&hints, &lifetimes, &rmap);
do_rewrites(b, &rmap, f);
/** @todo forward_hints(b, &hints, &rmap) */
}
vec_destroy(&hints);
vec_destroy(&lifetimes);
vec_destroy(&rmap);
}
|