summaryrefslogtreecommitdiffhomepage
path: root/ir/be/beasm.c
blob: 89eb87cdcd46cf70827fcde2e18143b81c2e50a3 (plain)
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
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
/*
 * This file is part of libFirm.
 * Copyright (C) 2015 University of Karlsruhe.
 */
#include "beasm.h"

#include "be_t.h"
#include "bearch.h"
#include "bediagnostic.h"
#include "beemitter.h"
#include "begnuas.h"
#include "beirg.h"
#include "benode.h"
#include "betranshlp.h"
#include "ident_t.h"
#include "panic.h"
#include "target_t.h"
#include "util.h"
#include "xmalloc.h"
#include <ctype.h>

arch_register_req_t const *be_make_register_req(struct obstack *obst, be_asm_constraint_t const *const c)
{
	assert(c->same_as < 0);
	assert(c->cls);

	if (c->all_registers_allowed)
		return c->cls->class_req;

	if (c->allowed_registers == 0)
		panic("constraint does not allow any registers");

	arch_register_req_t *const req     = (arch_register_req_t*)obstack_alloc(obst, sizeof(*req) + sizeof(unsigned));
	unsigned            *const limited = (unsigned*)(req + 1);
	*limited = c->allowed_registers;

	memset(req, 0, sizeof(*req));
	req->cls     = c->cls;
	req->limited = limited;
	req->width   = 1;
	return req;
}

void be_parse_asm_constraints_internal(be_asm_constraint_t *const constraint, ident *const constraint_text, parse_constraint_letter_func_t *const parse_constraint_letter, void const *const env)
{
	memset(constraint, 0, sizeof(*constraint));
	constraint->same_as = -1;

	char const *i = get_id_str(constraint_text);

	if (!i) {
		/* Labels have no constraint text. */
		constraint->all_registers_allowed = true;
		constraint->cls                   = &arch_exec_cls;
		return;
	}

	bool is_output = false;
	if (i[0] == '+' || i[0] == '=') {
		++i;
		is_output = true;
	}

	/* TODO: improve error messages with node and source info. (As users can
	 * easily hit these) */
	char                         immediate_type        = '\0';
	unsigned                     limited               = 0;
	arch_register_class_t const *cls                   = NULL;
	bool                         memory_possible       = false;
	bool                         all_registers_allowed = false;
	int                          same_as               = -1;
	while (*i != '\0') {
		switch (*i) {
		/* Skip spaces, out/in-out marker. */
		case ' ':
		case '\t':
		case '\n':
		case '%':
		case '&':
		case '*':
			++i;
			break;

		case '#':
			do {
				++i;
			} while (*i != '\0' && *i != ',');
			break;

		default:
			if (is_digit(*i)) {
				int p;
				if (sscanf(i, "%d%n", &same_as, &p) == 0) {
					be_errorf(NULL, "error while reading matching constraint in \"%s\"", constraint_text);
					i += 1;
				} else {
					i += p;
				}
			} else {
				be_asm_constraint_t new_constraint;
				memset(&new_constraint, 0, sizeof(new_constraint));
				parse_constraint_letter(env, &new_constraint, *i++);

				limited               |= new_constraint.allowed_registers;
				all_registers_allowed |= new_constraint.all_registers_allowed;
				memory_possible       |= new_constraint.memory_possible;

				arch_register_class_t const *const new_cls = new_constraint.cls;
				if (new_cls) {
					if (cls && cls != new_cls)
						be_warningf(NULL, "multiple register classes not supported in \"%s\"", constraint_text);
					cls = new_cls;
				}

				char const new_imm = new_constraint.immediate_type;
				if (new_imm != '\0') {
					if (immediate_type != '\0' && immediate_type != new_imm)
						be_warningf(NULL, "multiple immediate types not supported in \"%s\"", constraint_text);
					immediate_type = new_imm;
				}
			}
			break;
		}
	}

	if (same_as >= 0) {
		if (is_output) {
			be_warningf(NULL, "output constraint \"%s\" has matching constraint; ignoring it", constraint_text);
			same_as = -1;
		} else {
			if (cls) {
				be_warningf(NULL, "matching and register constraint not supported in constraint \"%s\"; ignoring register constraint", constraint_text);
				cls = NULL;
			}
			if (immediate_type != '\0') {
				be_warningf(NULL, "matching and immediate constraint not supported in constraint \"%s\"; ignoring immediate constraint", constraint_text);
				immediate_type = '\0';
			}
		}
	}

	if (!cls && same_as < 0 && !memory_possible)
		panic("no constraint specified for assembler input");

	constraint->same_as               = same_as;
	constraint->cls                   = cls;
	constraint->allowed_registers     = limited;
	constraint->all_registers_allowed = all_registers_allowed;
	constraint->memory_possible       = memory_possible;
	constraint->immediate_type        = immediate_type;
}

/* Determine number of output operands. */
static unsigned be_count_asm_outputs(ir_node const *const node)
{
	unsigned                       n_outputs   = pn_be_Asm_first_out; /* At least the default outputs. */
	ir_asm_constraint const *const constraints = get_ASM_constraints(node);
	for (unsigned i = 0, n = get_ASM_n_constraints(node); i != n; ++i) {
		n_outputs = MAX(n_outputs, (unsigned)(constraints[i].out_pos + 1));
	}
	return n_outputs;
}

be_asm_info_t be_asm_prepare_info(ir_node const *const node)
{
	unsigned                    const n_outs   = be_count_asm_outputs(node);
	arch_register_req_t const **const out_reqs = NEW_ARR_F(arch_register_req_t const*, n_outs);
	out_reqs[pn_be_Asm_M]         = arch_memory_req;
	out_reqs[pn_be_Asm_X_regular] = arch_exec_req;

	ir_node                   **const ins     = NEW_ARR_F(ir_node*, n_be_Asm_first_in);
	arch_register_req_t const **const in_reqs = NEW_ARR_F(arch_register_req_t const*, n_be_Asm_first_in);
	ins[n_be_Asm_mem]     = be_transform_node(get_ASM_mem(node));
	in_reqs[n_be_Asm_mem] = arch_memory_req;

	return (be_asm_info_t){
		.ins      = ins,
		.in_reqs  = in_reqs,
		.out_reqs = out_reqs,
	};
}

static bool can_match(arch_register_req_t const *const in, arch_register_req_t const *const out)
{
	if (in->cls != out->cls)
		return false;
	if (in->limited == NULL || out->limited == NULL)
		return true;
	return (*in->limited & *out->limited) != 0;
}

static bool match_requirement(arch_register_req_t const **reqs, size_t const n_reqs, bitset_t *const used, arch_register_req_t const *const req)
{
	for (size_t i = 0; i != n_reqs; ++i) {
		if (bitset_is_set(used, i))
			continue;
		if (!can_match(req, reqs[i]))
			continue;
		bitset_set(used, i);
		return true;
	}
	return false;
}

ir_node *be_make_asm(ir_node const *const node, be_asm_info_t const *const info, void *const operands)
{
	ir_node                   **in       = info->ins;
	arch_register_req_t const **in_reqs  = info->in_reqs;
	arch_register_req_t const **out_reqs = info->out_reqs;

	assert(ARR_LEN(in) == ARR_LEN(in_reqs));

	ir_graph       *const irg  = get_irn_irg(node);
	struct obstack *const obst = get_irg_obstack(irg);

	/* Handle early clobbers and labels. */
	size_t                         n_labels    = 0;
	size_t                   const orig_n_ins  = ARR_LEN(in_reqs);
	size_t                   const orig_n_outs = ARR_LEN(out_reqs);
	ir_asm_constraint const *const constraints = get_ASM_constraints(node);
	for (unsigned o = 0, n = get_ASM_n_constraints(node); o != n; ++o) {
		ir_asm_constraint const *const constraint = &constraints[o];
		if (!constraint->constraint) {
			++n_labels;
		} else if (strchr(get_id_str(constraint->constraint), '&')) {
			int const out_pos = constraint->out_pos;
			if (out_pos >= 0) {
				arch_register_req_t   const **const oslot = &out_reqs[out_pos];
				arch_register_req_t   const  *const oreq  = *oslot;
				arch_register_class_t const  *const cls   = oreq->cls;

				unsigned different = 0;

				/* Add each input in the same register class. */
				for (unsigned i = 0; i != orig_n_ins; ++i) {
					if (in_reqs[i]->cls == cls)
						different |= 1U << i;
				}

				/* Remove each input which has a matching output.
				 * The output already ensures that the register is different than the
				 * early clobber output. */
				for (unsigned i = 0; i != orig_n_outs; ++i) {
					arch_register_req_t const *const other_oreq = out_reqs[i];
					if (other_oreq->cls == cls) {
						unsigned const same_as = other_oreq->should_be_same;
						assert(is_po2_or_zero(same_as));
						different &= ~same_as;
					}
				}

				if (different != 0) {
					arch_register_req_t *const req = OALLOCZ(obst, arch_register_req_t);
					*req                   = *oreq;
					req->must_be_different = different;
					*oslot                 = req;
				}
			}
		}
	}

	size_t const n_clobbers = get_ASM_n_clobbers(node);
	if (n_clobbers != 0) {
		/* Collect clobbers and add them as outputs. */
		unsigned clobber_bits[ir_target.isa->n_register_classes];
		memset(&clobber_bits, 0, sizeof(clobber_bits));

		be_irg_t       *const birg             = be_birg_from_irg(irg);
		unsigned const *const allocatable_regs = birg->allocatable_regs;
		ident         **const clobbers         = get_ASM_clobbers(node);
		for (size_t i = 0; i < n_clobbers; ++i) {
			char            const *const clobber = get_id_str(clobbers[i]);
			arch_register_t const *const reg     = be_parse_register_name(clobber);
			if (reg && rbitset_is_set(allocatable_regs, reg->global_index)) {
				assert(reg->cls->n_regs <= sizeof(unsigned) * 8);
				if (!reg->cls->allow_clobber_input)
					clobber_bits[reg->cls->index] |= 1U << reg->index;
				ARR_APP1(arch_register_req_t const*, out_reqs, reg->single_req);
			}
		}

		/* Restrict inputs by clobbers. */
		for (size_t i = n_be_Asm_first_in, n = ARR_LEN(in_reqs); i != n; ++i) {
			arch_register_req_t   const *const req = in_reqs[i];
			arch_register_class_t const *const cls = req->cls;
			assert(cls->index < ARRAY_SIZE(clobber_bits));
			unsigned const clobber = clobber_bits[cls->index];
			if (clobber != 0) {
				arch_register_req_t *const new_req = (arch_register_req_t*)obstack_alloc(obst, sizeof(*new_req) + sizeof(unsigned));
				unsigned            *const limited = (unsigned*)(new_req + 1);
				if (req->limited) {
					*limited = *req->limited;
				} else {
					be_get_allocatable_regs(irg, cls, limited);
				}
				*limited        &= ~clobber;
				*new_req         = *req;
				new_req->limited = limited;
				in_reqs[i] = new_req;
			}
		}
	}

	unsigned const n_ins  = ARR_LEN(in);
	unsigned const n_outs = ARR_LEN(out_reqs); // Clobbers might add further outputs.

	/* Attempt to make ASM node register pressure faithful.
	 * (This does not work for complicated cases yet!)
	 *
	 * Algorithm: Check if there are fewer inputs or outputs (I will call this
	 * the smaller list). Then try to match each constraint of the smaller list
	 * to 1 of the other list. If we can't match it, then we add additional
	 * register pressure.
	 *
	 * FIXME: This is still broken in lots of cases. But at least better than
	 *        before...
	 * FIXME: need to do this per register class...
	 */
	be_add_pressure_t add_pressure[ir_target.isa->n_register_classes];
	memset(add_pressure, 0, sizeof(add_pressure));
	if (n_outs - n_labels - pn_be_Asm_first_out < n_ins - n_be_Asm_first_in) {
		bitset_t *const used_ins = bitset_alloca(n_ins);
		for (size_t o = pn_be_Asm_first_out; o < n_outs; ++o) {
			arch_register_req_t const *const outreq = out_reqs[o];
			if (!outreq->cls->manual_ra) {
				assert(outreq->cls->index < ARRAY_SIZE(add_pressure));
				if (!match_requirement(in_reqs, n_ins, used_ins, outreq))
					add_pressure[outreq->cls->index]++;
			}
		}
	} else {
		bitset_t *const used_outs = bitset_alloca(n_outs);
		for (unsigned i = n_be_Asm_first_in; i < n_ins; ++i) {
			arch_register_req_t const *const inreq = in_reqs[i];
			if (!inreq->cls->manual_ra) {
				assert(inreq->cls->index < ARRAY_SIZE(add_pressure));
				if (!match_requirement(out_reqs, n_outs, used_outs, inreq))
					add_pressure[inreq->cls->index]--;
			}
		}
	}

	dbg_info                   *const dbgi        = get_irn_dbg_info(node);
	ir_node                    *const block       = be_transform_nodes_block(node);
	ident                      *const text        = get_ASM_text(node);
	arch_register_req_t const **const dup_in_reqs = DUP_ARR_D(arch_register_req_t const*, obst, in_reqs);
	ir_node                    *const new_node    = be_new_Asm(dbgi, block, n_ins, in, dup_in_reqs, n_outs, text, operands);

	if (n_labels != 0) {
		ir_set_throws_exception(new_node, true);
		set_irn_pinned(new_node, op_pin_state_pinned);
	}

	for (unsigned i = 0; i != ARRAY_SIZE(add_pressure); ++i) {
		if (add_pressure[i] != 0) {
			arch_register_class_t const *const cls = &ir_target.isa->register_classes[i];
			arch_set_additional_pressure(new_node, cls, add_pressure[i]);
		}
	}

	backend_info_t *const be_info = be_get_info(new_node);
	for (size_t o = 0; o < n_outs; ++o) {
		be_info->out_infos[o].req = out_reqs[o];
	}

	DEL_ARR_F(in);
	DEL_ARR_F(in_reqs);
	DEL_ARR_F(out_reqs);

	return new_node;
}

ir_node *be_emit_asm(ir_node const *const asmn, be_emit_asm_operand_func *const emit_asm_operand)
{
	be_emit_cstring("#APP");
	be_emit_finish_line_gas(asmn);

	be_asm_attr_t const *const attr = get_be_asm_attr_const(asmn);

	char const *s = get_id_str(attr->text);
	if (s[0] != '\t')
		be_emit_char('\t');

	char     const *last       = s;
	unsigned const  n_operands = ARR_LEN(attr->operands);
	while ((s = strchr(s, '%'))) {
		be_emit_string_len(last, s - last);
		++s; /* Skip '%'. */
		switch (*s) {
		case '%':
		case '{':
		case '|':
		case '}':
			be_emit_char(*s++);
			break;

		case '=':
			/* From gcc source:
			 * %= outputs a number which is unique to each insn in the entire
			 * compilation.  This is useful for making local labels that are
			 * referred to more than once in a given insn. */
			++s; /* Skip '='. */
			be_emit_irprintf("%ld", get_irn_node_nr(asmn));
			break;

		default: {
			char const modifier = isalpha((unsigned char)*s) ? *s++ : '\0';
			unsigned   pos;
			int        p;
			if (sscanf(s, "%u%n", &pos, &p) == 1) {
				s += p;
				if (pos < n_operands) {
					emit_asm_operand(asmn, modifier, pos);
				} else {
					be_errorf(asmn, "asm operand number '%u' out of range", pos);
				}
			} else {
				be_errorf(asmn, "could not parse asm operand number");
			}
			break;
		}
		}

		last = s;
	}
	be_emit_string(last);

	be_emit_cstring("\n#NO_APP\n");
	be_emit_write_line();

	/* Return the fallthrough proj if present. */
	return get_Proj_for_pn(asmn, pn_be_Asm_X_regular);
}

static char const *be_get_constraint_name(be_asm_operand_kind_t const kind)
{
	switch (kind) {
	case BE_ASM_OPERAND_INVALID:      return "invalid";
	case BE_ASM_OPERAND_INPUT_VALUE:  return "input register";
	case BE_ASM_OPERAND_OUTPUT_VALUE: return "output register";
	case BE_ASM_OPERAND_IMMEDIATE:    return "immediate";
	case BE_ASM_OPERAND_MEMORY:       return "memory";
	case BE_ASM_OPERAND_LABEL:        return "label";
	}
	panic("invalid constraint kind");
}

bool be_is_valid_asm_operand_kind(ir_node const *const node, char const modifier, unsigned const pos, be_asm_operand_kind_t const have, char const *const mod_any, char const *const mod_imm, char const *const mod_mem)
{
	be_asm_operand_kind_t want;
	if (strchr(mod_any, modifier)) {
		return true;
	} else if (strchr(mod_imm, modifier)) {
		want = BE_ASM_OPERAND_IMMEDIATE;
	} else if (strchr(mod_mem, modifier)) {
		want = BE_ASM_OPERAND_MEMORY;
	} else if (modifier == 'l') {
		want = BE_ASM_OPERAND_LABEL;
	} else {
		be_errorf(node, "asm contains unknown modifier '%c'", modifier);
		return false;
	}
	if (want != have) {
		char const *const name_want = be_get_constraint_name(want);
		char const *const name_have = be_get_constraint_name(have);
		be_errorf(node, "modifier of operand '%%%c%u' requires an operand of type '%s', but got '%s'", modifier, pos, name_want, name_have);
		return false;
	}
	return true;
}

arch_register_t const *be_parse_register_name(char const *clobber)
{
	arch_isa_if_t const *const isa = ir_target.isa;

	/* GCC always accepts '#' and '%' as register name prefix.
	 * See strip_reg_name() in varasm.c. */
	char const c = clobber[0];
	if (c == '#' || c == '%' || (isa->register_prefix != '\0' && c == isa->register_prefix))
		clobber += 1;

	arch_register_t const *const reg = arch_find_register(clobber);
	if (reg)
		return reg;

	be_register_name_t const *const add = isa->additional_reg_names;
	if (add) {
		for (be_register_name_t const *i = add; i->name; ++i) {
			if (streq(i->name, clobber))
				return &isa->registers[i->index];
		}
	}

	return NULL;
}