automatic man page generation from tcc-doc.texi
[tinycc/miki.git] / tccasm.c
blob832bb243435a40ba07b9cca7965efc612055ac40
1 /*
2 * GAS like assembler for TCC
3 *
4 * Copyright (c) 2001, 2002 Fabrice Bellard
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program 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
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 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:
366 Sym *sym;
368 next();
369 sym = label_find(tok);
370 if (!sym) {
371 sym = label_push(&s1->asm_labels, tok, 0);
372 sym->type.t = VT_VOID;
374 sym->type.t &= ~VT_STATIC;
375 next();
377 break;
378 case TOK_ASM_string:
380 const uint8_t *p;
381 int i;
383 next();
384 if (tok != TOK_STR)
385 expect("string constant");
386 p = tokc.cstr->data;
387 for(i = 0; i < tokc.cstr->size; i++)
388 g(p[i]);
389 next();
391 break;
392 case TOK_ASM_text:
393 case TOK_ASM_data:
394 case TOK_ASM_bss:
396 char sname[64];
397 tok1 = tok;
398 n = 0;
399 next();
400 if (tok != ';' && tok != TOK_LINEFEED) {
401 n = asm_int_expr(s1);
402 next();
404 sprintf(sname, (n?".%s%d":".%s"), get_tok_str(tok1, NULL), n);
405 use_section(s1, sname);
407 break;
408 case TOK_SECTION1:
410 char sname[256];
412 /* XXX: support more options */
413 next();
414 sname[0] = '\0';
415 while (tok != ';' && tok != TOK_LINEFEED && tok != ',') {
416 if (tok == TOK_STR)
417 pstrcat(sname, sizeof(sname), tokc.cstr->data);
418 else
419 pstrcat(sname, sizeof(sname), get_tok_str(tok, NULL));
420 next();
422 use_section(s1, sname);
424 break;
425 default:
426 error("unknown assembler directive '.%s'", get_tok_str(tok, NULL));
427 break;
432 /* assemble a file */
433 static int tcc_assemble_internal(TCCState *s1, int do_preprocess)
435 int opcode;
437 #if 0
438 /* print stats about opcodes */
440 const ASMInstr *pa;
441 int freq[4];
442 int op_vals[500];
443 int nb_op_vals, i, j;
445 nb_op_vals = 0;
446 memset(freq, 0, sizeof(freq));
447 for(pa = asm_instrs; pa->sym != 0; pa++) {
448 freq[pa->nb_ops]++;
449 for(i=0;i<pa->nb_ops;i++) {
450 for(j=0;j<nb_op_vals;j++) {
451 if (pa->op_type[i] == op_vals[j])
452 goto found;
454 op_vals[nb_op_vals++] = pa->op_type[i];
455 found: ;
458 for(i=0;i<nb_op_vals;i++) {
459 int v = op_vals[i];
460 if ((v & (v - 1)) != 0)
461 printf("%3d: %08x\n", i, v);
463 printf("size=%d nb=%d f0=%d f1=%d f2=%d f3=%d\n",
464 sizeof(asm_instrs), sizeof(asm_instrs) / sizeof(ASMInstr),
465 freq[0], freq[1], freq[2], freq[3]);
467 #endif
469 /* XXX: undefine C labels */
471 ch = file->buf_ptr[0];
472 tok_flags = TOK_FLAG_BOL | TOK_FLAG_BOF;
473 parse_flags = 0;
474 if (do_preprocess)
475 parse_flags |= PARSE_FLAG_PREPROCESS;
476 next();
477 for(;;) {
478 if (tok == TOK_EOF)
479 break;
480 parse_flags |= PARSE_FLAG_LINEFEED; /* XXX: suppress that hack */
481 redo:
482 if (tok == '#') {
483 /* horrible gas comment */
484 while (tok != TOK_LINEFEED)
485 next();
486 } else if (tok == '.') {
487 asm_parse_directive(s1);
488 } else if (tok == TOK_PPNUM) {
489 const char *p;
490 int n;
491 p = tokc.cstr->data;
492 n = strtoul(p, (char **)&p, 10);
493 if (*p != '\0')
494 expect("':'");
495 /* new local label */
496 asm_new_label(s1, asm_get_local_label_name(s1, n), 1);
497 next();
498 skip(':');
499 goto redo;
500 } else if (tok >= TOK_IDENT) {
501 /* instruction or label */
502 opcode = tok;
503 next();
504 if (tok == ':') {
505 /* new label */
506 asm_new_label(s1, opcode, 0);
507 next();
508 goto redo;
509 } else {
510 asm_opcode(s1, opcode);
513 /* end of line */
514 if (tok != ';' && tok != TOK_LINEFEED){
515 expect("end of line");
517 parse_flags &= ~PARSE_FLAG_LINEFEED; /* XXX: suppress that hack */
518 next();
521 asm_free_labels(s1);
523 return 0;
526 /* Assemble the current file */
527 static int tcc_assemble(TCCState *s1, int do_preprocess)
529 int ret;
531 preprocess_init(s1);
533 /* default section is text */
534 cur_text_section = text_section;
535 ind = cur_text_section->data_offset;
537 ret = tcc_assemble_internal(s1, do_preprocess);
539 cur_text_section->data_offset = ind;
540 return ret;
543 /********************************************************************/
544 /* GCC inline asm support */
546 /* assemble the string 'str' in the current C compilation unit without
547 C preprocessing. NOTE: str is modified by modifying the '\0' at the
548 end */
549 static void tcc_assemble_inline(TCCState *s1, char *str, int len)
551 BufferedFile *bf, *saved_file;
552 int saved_parse_flags, *saved_macro_ptr;
554 bf = tcc_malloc(sizeof(BufferedFile));
555 memset(bf, 0, sizeof(BufferedFile));
556 bf->fd = -1;
557 bf->buf_ptr = str;
558 bf->buf_end = str + len;
559 str[len] = CH_EOB;
560 /* same name as current file so that errors are correctly
561 reported */
562 pstrcpy(bf->filename, sizeof(bf->filename), file->filename);
563 bf->line_num = file->line_num;
564 saved_file = file;
565 file = bf;
566 saved_parse_flags = parse_flags;
567 saved_macro_ptr = macro_ptr;
568 macro_ptr = NULL;
570 tcc_assemble_internal(s1, 0);
572 parse_flags = saved_parse_flags;
573 macro_ptr = saved_macro_ptr;
574 file = saved_file;
575 tcc_free(bf);
578 /* find a constraint by its number or id (gcc 3 extended
579 syntax). return -1 if not found. Return in *pp in char after the
580 constraint */
581 static int find_constraint(ASMOperand *operands, int nb_operands,
582 const char *name, const char **pp)
584 int index;
585 TokenSym *ts;
586 const char *p;
588 if (isnum(*name)) {
589 index = 0;
590 while (isnum(*name)) {
591 index = (index * 10) + (*name) - '0';
592 name++;
594 if ((unsigned)index >= nb_operands)
595 index = -1;
596 } else if (*name == '[') {
597 name++;
598 p = strchr(name, ']');
599 if (p) {
600 ts = tok_alloc(name, p - name);
601 for(index = 0; index < nb_operands; index++) {
602 if (operands[index].id == ts->tok)
603 goto found;
605 index = -1;
606 found:
607 name = p + 1;
608 } else {
609 index = -1;
611 } else {
612 index = -1;
614 if (pp)
615 *pp = name;
616 return index;
619 static void subst_asm_operands(ASMOperand *operands, int nb_operands,
620 int nb_outputs,
621 CString *out_str, CString *in_str)
623 int c, index, modifier;
624 const char *str;
625 ASMOperand *op;
626 SValue sv;
628 cstr_new(out_str);
629 str = in_str->data;
630 for(;;) {
631 c = *str++;
632 if (c == '%') {
633 if (*str == '%') {
634 str++;
635 goto add_char;
637 modifier = 0;
638 if (*str == 'c' || *str == 'n' ||
639 *str == 'b' || *str == 'w' || *str == 'h')
640 modifier = *str++;
641 index = find_constraint(operands, nb_operands, str, &str);
642 if (index < 0)
643 error("invalid operand reference after %%");
644 op = &operands[index];
645 sv = *op->vt;
646 if (op->reg >= 0) {
647 sv.r = op->reg;
648 if ((op->vt->r & VT_VALMASK) == VT_LLOCAL)
649 sv.r |= VT_LVAL;
651 subst_asm_operand(out_str, &sv, modifier);
652 } else {
653 add_char:
654 cstr_ccat(out_str, c);
655 if (c == '\0')
656 break;
662 static void parse_asm_operands(ASMOperand *operands, int *nb_operands_ptr,
663 int is_output)
665 ASMOperand *op;
666 int nb_operands;
668 if (tok != ':') {
669 nb_operands = *nb_operands_ptr;
670 for(;;) {
671 if (nb_operands >= MAX_ASM_OPERANDS)
672 error("too many asm operands");
673 op = &operands[nb_operands++];
674 op->id = 0;
675 if (tok == '[') {
676 next();
677 if (tok < TOK_IDENT)
678 expect("identifier");
679 op->id = tok;
680 next();
681 skip(']');
683 if (tok != TOK_STR)
684 expect("string constant");
685 op->constraint = tcc_malloc(tokc.cstr->size);
686 strcpy(op->constraint, tokc.cstr->data);
687 next();
688 skip('(');
689 gexpr();
690 if (is_output) {
691 test_lvalue();
692 } else {
693 /* we want to avoid LLOCAL case. note that it may come
694 from register storage, so we need to convert (reg)
695 case */
696 if ((vtop->r & VT_LVAL) &&
697 ((vtop->r & VT_VALMASK) == VT_LLOCAL ||
698 (vtop->r & VT_VALMASK) < VT_CONST)) {
699 gv(RC_INT);
702 op->vt = vtop;
703 skip(')');
704 if (tok == ',') {
705 next();
706 } else {
707 break;
710 *nb_operands_ptr = nb_operands;
714 /* parse the GCC asm() instruction */
715 static void asm_instr(void)
717 CString astr, astr1;
718 ASMOperand operands[MAX_ASM_OPERANDS];
719 int nb_inputs, nb_outputs, nb_operands, i;
720 uint8_t input_regs_allocated[NB_ASM_REGS];
721 uint8_t output_regs_allocated[NB_ASM_REGS];
722 uint8_t clobber_regs[NB_ASM_REGS];
724 next();
725 /* since we always generate the asm() instruction, we can ignore
726 volatile */
727 if (tok == TOK_VOLATILE1 || tok == TOK_VOLATILE2 || tok == TOK_VOLATILE3) {
728 next();
730 skip('(');
731 /* read the string */
732 if (tok != TOK_STR)
733 expect("string constant");
734 cstr_new(&astr);
735 while (tok == TOK_STR) {
736 /* XXX: add \0 handling too ? */
737 cstr_cat(&astr, tokc.cstr->data);
738 next();
740 cstr_ccat(&astr, '\0');
741 nb_operands = 0;
742 nb_outputs = 0;
743 memset(clobber_regs, 0, sizeof(clobber_regs));
744 if (tok == ':') {
745 next();
746 /* output args */
747 parse_asm_operands(operands, &nb_operands, 1);
748 nb_outputs = nb_operands;
749 if (tok == ':') {
750 next();
751 /* input args */
752 parse_asm_operands(operands, &nb_operands, 0);
753 if (tok == ':') {
754 /* clobber list */
755 /* XXX: handle registers */
756 next();
757 for(;;) {
758 if (tok != TOK_STR)
759 expect("string constant");
760 asm_clobber(clobber_regs, tokc.cstr->data);
761 next();
762 if (tok == ',') {
763 next();
764 } else {
765 break;
771 skip(')');
772 /* NOTE: we do not eat the ';' so that we can restore the current
773 token after the assembler parsing */
774 if (tok != ';')
775 expect("';'");
776 nb_inputs = nb_operands - nb_outputs;
778 /* save all values in the memory */
779 save_regs(0);
781 /* compute constraints */
782 asm_compute_constraints(input_regs_allocated,
783 operands, nb_operands, nb_outputs, 0,
784 NULL);
785 asm_compute_constraints(output_regs_allocated,
786 operands, nb_operands, nb_outputs, 1,
787 input_regs_allocated);
789 /* substitute the operands in the asm string. No substitution is
790 done if no operands (GCC behaviour) */
791 #ifdef ASM_DEBUG
792 printf("asm: \"%s\"\n", (char *)astr.data);
793 #endif
794 if (nb_operands > 0) {
795 subst_asm_operands(operands, nb_operands, nb_outputs, &astr1, &astr);
796 cstr_free(&astr);
797 } else {
798 astr1 = astr;
800 #ifdef ASM_DEBUG
801 printf("subst_asm: \"%s\"\n", (char *)astr1.data);
802 #endif
804 /* generate loads */
805 asm_gen_code(operands, nb_operands, nb_outputs, 0, clobber_regs);
807 /* assemble the string with tcc internal assembler */
808 tcc_assemble_inline(tcc_state, astr1.data, astr1.size - 1);
810 /* restore the current C token */
811 next();
813 /* store the output values if needed */
814 asm_gen_code(operands, nb_operands, nb_outputs, 1, clobber_regs);
816 /* free everything */
817 for(i=0;i<nb_operands;i++) {
818 ASMOperand *op;
819 op = &operands[i];
820 tcc_free(op->constraint);
821 vpop();
823 cstr_free(&astr1);