copy paste abuse
[tinycc.git] / tccasm.c
blobc3f7e353bfc94c09e91a3e19e5e6ca7d22142bd2
1 /*
2 * GAS like assembler for TCC
3 *
4 * Copyright (c) 2001, 2002 Fabrice Bellard
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 static int asm_get_local_label_name(TCCState *s1, unsigned int n)
23 char buf[64];
24 TokenSym *ts;
26 snprintf(buf, sizeof(buf), "L..%u", n);
27 ts = tok_alloc(buf, strlen(buf));
28 return ts->tok;
31 /* We do not use the C expression parser to handle symbols. Maybe the
32 C expression parser could be tweaked to do so. */
34 static void asm_expr_unary(TCCState *s1, ExprValue *pe)
36 Sym *sym;
37 int op, n, label;
38 const char *p;
40 switch(tok) {
41 case TOK_PPNUM:
42 p = tokc.cstr->data;
43 n = strtoul(p, (char **)&p, 0);
44 if (*p == 'b' || *p == 'f') {
45 /* backward or forward label */
46 label = asm_get_local_label_name(s1, n);
47 sym = label_find(label);
48 if (*p == 'b') {
49 /* backward : find the last corresponding defined label */
50 if (sym && sym->r == 0)
51 sym = sym->prev_tok;
52 if (!sym)
53 error("local label '%d' not found backward", n);
54 } else {
55 /* forward */
56 if (!sym || sym->r) {
57 /* if the last label is defined, then define a new one */
58 sym = label_push(&s1->asm_labels, label, 0);
59 sym->type.t = VT_STATIC | VT_VOID;
62 pe->v = 0;
63 pe->sym = sym;
64 } else if (*p == '\0') {
65 pe->v = n;
66 pe->sym = NULL;
67 } else {
68 error("invalid number syntax");
70 next();
71 break;
72 case '+':
73 next();
74 asm_expr_unary(s1, pe);
75 break;
76 case '-':
77 case '~':
78 op = tok;
79 next();
80 asm_expr_unary(s1, pe);
81 if (pe->sym)
82 error("invalid operation with label");
83 if (op == '-')
84 pe->v = -pe->v;
85 else
86 pe->v = ~pe->v;
87 break;
88 case TOK_CCHAR:
89 case TOK_LCHAR:
90 pe->v = tokc.i;
91 pe->sym = NULL;
92 next();
93 break;
94 default:
95 if (tok >= TOK_IDENT) {
96 /* label case : if the label was not found, add one */
97 sym = label_find(tok);
98 if (!sym) {
99 sym = label_push(&s1->asm_labels, tok, 0);
100 /* NOTE: by default, the symbol is global */
101 sym->type.t = VT_VOID;
103 pe->v = 0;
104 pe->sym = sym;
105 next();
106 } else {
107 error("bad expression syntax [%s]", get_tok_str(tok, &tokc));
109 break;
113 static void asm_expr_prod(TCCState *s1, ExprValue *pe)
115 int op;
116 ExprValue e2;
118 asm_expr_unary(s1, pe);
119 for(;;) {
120 op = tok;
121 if (op != '*' && op != '/' && op != '%' &&
122 op != TOK_SHL && op != TOK_SAR)
123 break;
124 next();
125 asm_expr_unary(s1, &e2);
126 if (pe->sym || e2.sym)
127 error("invalid operation with label");
128 switch(op) {
129 case '*':
130 pe->v *= e2.v;
131 break;
132 case '/':
133 if (e2.v == 0) {
134 div_error:
135 error("division by zero");
137 pe->v /= e2.v;
138 break;
139 case '%':
140 if (e2.v == 0)
141 goto div_error;
142 pe->v %= e2.v;
143 break;
144 case TOK_SHL:
145 pe->v <<= e2.v;
146 break;
147 default:
148 case TOK_SAR:
149 pe->v >>= e2.v;
150 break;
155 static void asm_expr_logic(TCCState *s1, ExprValue *pe)
157 int op;
158 ExprValue e2;
160 asm_expr_prod(s1, pe);
161 for(;;) {
162 op = tok;
163 if (op != '&' && op != '|' && op != '^')
164 break;
165 next();
166 asm_expr_prod(s1, &e2);
167 if (pe->sym || e2.sym)
168 error("invalid operation with label");
169 switch(op) {
170 case '&':
171 pe->v &= e2.v;
172 break;
173 case '|':
174 pe->v |= e2.v;
175 break;
176 default:
177 case '^':
178 pe->v ^= e2.v;
179 break;
184 static inline void asm_expr_sum(TCCState *s1, ExprValue *pe)
186 int op;
187 ExprValue e2;
189 asm_expr_logic(s1, pe);
190 for(;;) {
191 op = tok;
192 if (op != '+' && op != '-')
193 break;
194 next();
195 asm_expr_logic(s1, &e2);
196 if (op == '+') {
197 if (pe->sym != NULL && e2.sym != NULL)
198 goto cannot_relocate;
199 pe->v += e2.v;
200 if (pe->sym == NULL && e2.sym != NULL)
201 pe->sym = e2.sym;
202 } else {
203 pe->v -= e2.v;
204 /* NOTE: we are less powerful than gas in that case
205 because we store only one symbol in the expression */
206 if (!pe->sym && !e2.sym) {
207 /* OK */
208 } else if (pe->sym && !e2.sym) {
209 /* OK */
210 } else if (pe->sym && e2.sym) {
211 if (pe->sym == e2.sym) {
212 /* OK */
213 } else if (pe->sym->r == e2.sym->r && pe->sym->r != 0) {
214 /* we also accept defined symbols in the same section */
215 pe->v += (long)pe->sym->next - (long)e2.sym->next;
216 } else {
217 goto cannot_relocate;
219 pe->sym = NULL; /* same symbols can be substracted to NULL */
220 } else {
221 cannot_relocate:
222 error("invalid operation with label");
228 static void asm_expr(TCCState *s1, ExprValue *pe)
230 asm_expr_sum(s1, pe);
233 static int asm_int_expr(TCCState *s1)
235 ExprValue e;
236 asm_expr(s1, &e);
237 if (e.sym)
238 expect("constant");
239 return e.v;
242 /* NOTE: the same name space as C labels is used to avoid using too
243 much memory when storing labels in TokenStrings */
244 static void asm_new_label(TCCState *s1, int label, int is_local)
246 Sym *sym;
248 sym = label_find(label);
249 if (sym) {
250 if (sym->r) {
251 /* the label is already defined */
252 if (!is_local) {
253 error("assembler label '%s' already defined",
254 get_tok_str(label, NULL));
255 } else {
256 /* redefinition of local labels is possible */
257 goto new_label;
260 } else {
261 new_label:
262 sym = label_push(&s1->asm_labels, label, 0);
263 sym->type.t = VT_STATIC | VT_VOID;
265 sym->r = cur_text_section->sh_num;
266 sym->next = (void *)ind;
269 static void asm_free_labels(TCCState *st)
271 Sym *s, *s1;
272 for(s = st->asm_labels; s != NULL; s = s1) {
273 s1 = s->prev;
274 /* define symbol value in object file */
275 if (s->r) {
276 put_extern_sym(s, st->sections[s->r], (long)s->next, 0);
278 /* remove label */
279 table_ident[s->v - TOK_IDENT]->sym_label = NULL;
280 tcc_free(s);
282 st->asm_labels = NULL;
285 static void use_section(TCCState *s1, const char *name)
287 Section *sec;
288 sec = find_section(s1, name);
289 cur_text_section->data_offset = ind;
290 cur_text_section = sec;
291 ind = cur_text_section->data_offset;
294 static void asm_parse_directive(TCCState *s1)
296 int n, offset, v, size, tok1;
297 Section *sec;
298 uint8_t *ptr;
300 /* assembler directive */
301 next();
302 sec = cur_text_section;
303 switch(tok) {
304 case TOK_ASM_align:
305 case TOK_ASM_skip:
306 case TOK_ASM_space:
307 tok1 = tok;
308 next();
309 n = asm_int_expr(s1);
310 if (tok1 == TOK_ASM_align) {
311 if (n < 0 || (n & (n-1)) != 0)
312 error("alignment must be a positive power of two");
313 offset = (ind + n - 1) & -n;
314 size = offset - ind;
315 } else {
316 size = n;
318 v = 0;
319 if (tok == ',') {
320 next();
321 v = asm_int_expr(s1);
323 if (sec->sh_type != SHT_NOBITS) {
324 sec->data_offset = ind;
325 ptr = section_ptr_add(sec, size);
326 memset(ptr, v, size);
328 ind += size;
329 break;
330 case TOK_ASM_byte:
331 size = 1;
332 goto asm_data;
333 case TOK_ASM_word:
334 case TOK_SHORT:
335 size = 2;
336 goto asm_data;
337 case TOK_LONG:
338 case TOK_INT:
339 size = 4;
340 asm_data:
341 next();
342 for(;;) {
343 ExprValue e;
344 asm_expr(s1, &e);
345 if (sec->sh_type != SHT_NOBITS) {
346 if (size == 4) {
347 gen_expr32(&e);
348 } else {
349 if (e.sym)
350 expect("constant");
351 if (size == 1)
352 g(e.v);
353 else
354 gen_le16(e.v);
356 } else {
357 ind += size;
359 if (tok != ',')
360 break;
361 next();
363 break;
364 case TOK_ASM_globl:
365 case TOK_ASM_global:
367 Sym *sym;
369 next();
370 sym = label_find(tok);
371 if (!sym) {
372 sym = label_push(&s1->asm_labels, tok, 0);
373 sym->type.t = VT_VOID;
375 sym->type.t &= ~VT_STATIC;
376 next();
378 break;
379 case TOK_ASM_string:
381 const uint8_t *p;
382 int i;
384 next();
385 if (tok != TOK_STR)
386 expect("string constant");
387 p = tokc.cstr->data;
388 for(i = 0; i < tokc.cstr->size; i++)
389 g(p[i]);
390 next();
392 break;
393 case TOK_ASM_text:
394 case TOK_ASM_data:
395 case TOK_ASM_bss:
397 char sname[64];
398 tok1 = tok;
399 n = 0;
400 next();
401 if (tok != ';' && tok != TOK_LINEFEED) {
402 n = asm_int_expr(s1);
403 next();
405 sprintf(sname, (n?".%s%d":".%s"), get_tok_str(tok1, NULL), n);
406 use_section(s1, sname);
408 break;
409 case TOK_SECTION1:
411 char sname[256];
413 /* XXX: support more options */
414 next();
415 sname[0] = '\0';
416 while (tok != ';' && tok != TOK_LINEFEED && tok != ',') {
417 if (tok == TOK_STR)
418 pstrcat(sname, sizeof(sname), tokc.cstr->data);
419 else
420 pstrcat(sname, sizeof(sname), get_tok_str(tok, NULL));
421 next();
423 use_section(s1, sname);
425 break;
426 default:
427 error("unknown assembler directive '.%s'", get_tok_str(tok, NULL));
428 break;
433 /* assemble a file */
434 static int tcc_assemble_internal(TCCState *s1, int do_preprocess)
436 int opcode;
438 #if 0
439 /* print stats about opcodes */
441 const ASMInstr *pa;
442 int freq[4];
443 int op_vals[500];
444 int nb_op_vals, i, j;
446 nb_op_vals = 0;
447 memset(freq, 0, sizeof(freq));
448 for(pa = asm_instrs; pa->sym != 0; pa++) {
449 freq[pa->nb_ops]++;
450 for(i=0;i<pa->nb_ops;i++) {
451 for(j=0;j<nb_op_vals;j++) {
452 if (pa->op_type[i] == op_vals[j])
453 goto found;
455 op_vals[nb_op_vals++] = pa->op_type[i];
456 found: ;
459 for(i=0;i<nb_op_vals;i++) {
460 int v = op_vals[i];
461 if ((v & (v - 1)) != 0)
462 printf("%3d: %08x\n", i, v);
464 printf("size=%d nb=%d f0=%d f1=%d f2=%d f3=%d\n",
465 sizeof(asm_instrs), sizeof(asm_instrs) / sizeof(ASMInstr),
466 freq[0], freq[1], freq[2], freq[3]);
468 #endif
470 /* XXX: undefine C labels */
472 ch = file->buf_ptr[0];
473 tok_flags = TOK_FLAG_BOL | TOK_FLAG_BOF;
474 parse_flags = 0;
475 if (do_preprocess)
476 parse_flags |= PARSE_FLAG_PREPROCESS;
477 next();
478 for(;;) {
479 if (tok == TOK_EOF)
480 break;
481 parse_flags |= PARSE_FLAG_LINEFEED; /* XXX: suppress that hack */
482 redo:
483 if (tok == '#') {
484 /* horrible gas comment */
485 while (tok != TOK_LINEFEED)
486 next();
487 } else if (tok == '.') {
488 asm_parse_directive(s1);
489 } else if (tok == TOK_PPNUM) {
490 const char *p;
491 int n;
492 p = tokc.cstr->data;
493 n = strtoul(p, (char **)&p, 10);
494 if (*p != '\0')
495 expect("':'");
496 /* new local label */
497 asm_new_label(s1, asm_get_local_label_name(s1, n), 1);
498 next();
499 skip(':');
500 goto redo;
501 } else if (tok >= TOK_IDENT) {
502 /* instruction or label */
503 opcode = tok;
504 next();
505 if (tok == ':') {
506 /* new label */
507 asm_new_label(s1, opcode, 0);
508 next();
509 goto redo;
510 } else {
511 asm_opcode(s1, opcode);
514 /* end of line */
515 if (tok != ';' && tok != TOK_LINEFEED){
516 expect("end of line");
518 parse_flags &= ~PARSE_FLAG_LINEFEED; /* XXX: suppress that hack */
519 next();
522 asm_free_labels(s1);
524 return 0;
527 /* Assemble the current file */
528 static int tcc_assemble(TCCState *s1, int do_preprocess)
530 int ret;
532 preprocess_init(s1);
534 /* default section is text */
535 cur_text_section = text_section;
536 ind = cur_text_section->data_offset;
538 ret = tcc_assemble_internal(s1, do_preprocess);
540 cur_text_section->data_offset = ind;
541 return ret;
544 /********************************************************************/
545 /* GCC inline asm support */
547 /* assemble the string 'str' in the current C compilation unit without
548 C preprocessing. NOTE: str is modified by modifying the '\0' at the
549 end */
550 static void tcc_assemble_inline(TCCState *s1, char *str, int len)
552 BufferedFile *bf, *saved_file;
553 int saved_parse_flags, *saved_macro_ptr;
555 bf = tcc_malloc(sizeof(BufferedFile));
556 memset(bf, 0, sizeof(BufferedFile));
557 bf->fd = -1;
558 bf->buf_ptr = str;
559 bf->buf_end = str + len;
560 str[len] = CH_EOB;
561 /* same name as current file so that errors are correctly
562 reported */
563 pstrcpy(bf->filename, sizeof(bf->filename), file->filename);
564 bf->line_num = file->line_num;
565 saved_file = file;
566 file = bf;
567 saved_parse_flags = parse_flags;
568 saved_macro_ptr = macro_ptr;
569 macro_ptr = NULL;
571 tcc_assemble_internal(s1, 0);
573 parse_flags = saved_parse_flags;
574 macro_ptr = saved_macro_ptr;
575 file = saved_file;
576 tcc_free(bf);
579 /* find a constraint by its number or id (gcc 3 extended
580 syntax). return -1 if not found. Return in *pp in char after the
581 constraint */
582 static int find_constraint(ASMOperand *operands, int nb_operands,
583 const char *name, const char **pp)
585 int index;
586 TokenSym *ts;
587 const char *p;
589 if (isnum(*name)) {
590 index = 0;
591 while (isnum(*name)) {
592 index = (index * 10) + (*name) - '0';
593 name++;
595 if ((unsigned)index >= nb_operands)
596 index = -1;
597 } else if (*name == '[') {
598 name++;
599 p = strchr(name, ']');
600 if (p) {
601 ts = tok_alloc(name, p - name);
602 for(index = 0; index < nb_operands; index++) {
603 if (operands[index].id == ts->tok)
604 goto found;
606 index = -1;
607 found:
608 name = p + 1;
609 } else {
610 index = -1;
612 } else {
613 index = -1;
615 if (pp)
616 *pp = name;
617 return index;
620 static void subst_asm_operands(ASMOperand *operands, int nb_operands,
621 int nb_outputs,
622 CString *out_str, CString *in_str)
624 int c, index, modifier;
625 const char *str;
626 ASMOperand *op;
627 SValue sv;
629 cstr_new(out_str);
630 str = in_str->data;
631 for(;;) {
632 c = *str++;
633 if (c == '%') {
634 if (*str == '%') {
635 str++;
636 goto add_char;
638 modifier = 0;
639 if (*str == 'c' || *str == 'n' ||
640 *str == 'b' || *str == 'w' || *str == 'h')
641 modifier = *str++;
642 index = find_constraint(operands, nb_operands, str, &str);
643 if (index < 0)
644 error("invalid operand reference after %%");
645 op = &operands[index];
646 sv = *op->vt;
647 if (op->reg >= 0) {
648 sv.r = op->reg;
649 if ((op->vt->r & VT_VALMASK) == VT_LLOCAL)
650 sv.r |= VT_LVAL;
652 subst_asm_operand(out_str, &sv, modifier);
653 } else {
654 add_char:
655 cstr_ccat(out_str, c);
656 if (c == '\0')
657 break;
663 static void parse_asm_operands(ASMOperand *operands, int *nb_operands_ptr,
664 int is_output)
666 ASMOperand *op;
667 int nb_operands;
669 if (tok != ':') {
670 nb_operands = *nb_operands_ptr;
671 for(;;) {
672 if (nb_operands >= MAX_ASM_OPERANDS)
673 error("too many asm operands");
674 op = &operands[nb_operands++];
675 op->id = 0;
676 if (tok == '[') {
677 next();
678 if (tok < TOK_IDENT)
679 expect("identifier");
680 op->id = tok;
681 next();
682 skip(']');
684 if (tok != TOK_STR)
685 expect("string constant");
686 op->constraint = tcc_malloc(tokc.cstr->size);
687 strcpy(op->constraint, tokc.cstr->data);
688 next();
689 skip('(');
690 gexpr();
691 if (is_output) {
692 test_lvalue();
693 } else {
694 /* we want to avoid LLOCAL case. note that it may come
695 from register storage, so we need to convert (reg)
696 case */
697 if ((vtop->r & VT_LVAL) &&
698 ((vtop->r & VT_VALMASK) == VT_LLOCAL ||
699 (vtop->r & VT_VALMASK) < VT_CONST)) {
700 gv(RC_INT);
703 op->vt = vtop;
704 skip(')');
705 if (tok == ',') {
706 next();
707 } else {
708 break;
711 *nb_operands_ptr = nb_operands;
715 /* parse the GCC asm() instruction */
716 static void asm_instr(void)
718 CString astr, astr1;
719 ASMOperand operands[MAX_ASM_OPERANDS];
720 int nb_inputs, nb_outputs, nb_operands, i;
721 uint8_t input_regs_allocated[NB_ASM_REGS];
722 uint8_t output_regs_allocated[NB_ASM_REGS];
723 uint8_t clobber_regs[NB_ASM_REGS];
725 next();
726 /* since we always generate the asm() instruction, we can ignore
727 volatile */
728 if (tok == TOK_VOLATILE1 || tok == TOK_VOLATILE2 || tok == TOK_VOLATILE3) {
729 next();
731 skip('(');
732 /* read the string */
733 if (tok != TOK_STR)
734 expect("string constant");
735 cstr_new(&astr);
736 while (tok == TOK_STR) {
737 /* XXX: add \0 handling too ? */
738 cstr_cat(&astr, tokc.cstr->data);
739 next();
741 cstr_ccat(&astr, '\0');
742 nb_operands = 0;
743 nb_outputs = 0;
744 memset(clobber_regs, 0, sizeof(clobber_regs));
745 if (tok == ':') {
746 next();
747 /* output args */
748 parse_asm_operands(operands, &nb_operands, 1);
749 nb_outputs = nb_operands;
750 if (tok == ':') {
751 next();
752 /* input args */
753 parse_asm_operands(operands, &nb_operands, 0);
754 if (tok == ':') {
755 /* clobber list */
756 /* XXX: handle registers */
757 next();
758 for(;;) {
759 if (tok != TOK_STR)
760 expect("string constant");
761 asm_clobber(clobber_regs, tokc.cstr->data);
762 next();
763 if (tok == ',') {
764 next();
765 } else {
766 break;
772 skip(')');
773 /* NOTE: we do not eat the ';' so that we can restore the current
774 token after the assembler parsing */
775 if (tok != ';')
776 expect("';'");
777 nb_inputs = nb_operands - nb_outputs;
779 /* save all values in the memory */
780 save_regs(0);
782 /* compute constraints */
783 asm_compute_constraints(input_regs_allocated,
784 operands, nb_operands, nb_outputs, 0,
785 NULL);
786 asm_compute_constraints(output_regs_allocated,
787 operands, nb_operands, nb_outputs, 1,
788 input_regs_allocated);
790 /* substitute the operands in the asm string. No substitution is
791 done if no operands (GCC behaviour) */
792 #ifdef ASM_DEBUG
793 printf("asm: \"%s\"\n", (char *)astr.data);
794 #endif
795 if (nb_operands > 0) {
796 subst_asm_operands(operands, nb_operands, nb_outputs, &astr1, &astr);
797 cstr_free(&astr);
798 } else {
799 astr1 = astr;
801 #ifdef ASM_DEBUG
802 printf("subst_asm: \"%s\"\n", (char *)astr1.data);
803 #endif
805 /* generate loads */
806 asm_gen_code(operands, nb_operands, nb_outputs, 0, clobber_regs);
808 /* assemble the string with tcc internal assembler */
809 tcc_assemble_inline(tcc_state, astr1.data, astr1.size - 1);
811 /* restore the current C token */
812 next();
814 /* store the output values if needed */
815 asm_gen_code(operands, nb_operands, nb_outputs, 1, clobber_regs);
817 /* free everything */
818 for(i=0;i<nb_operands;i++) {
819 ASMOperand *op;
820 op = &operands[i];
821 tcc_free(op->constraint);
822 vpop();
824 cstr_free(&astr1);