/* * GAS like assembler for TCC * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "tcc.h" #ifdef CONFIG_TCC_ASM ST_FUNC int asm_get_local_label_name(TCCState *s1, unsigned int n) { char buf[64]; TokenSym *ts; snprintf(buf, sizeof(buf), "L..%u", n); ts = tok_alloc(buf, strlen(buf)); return ts->tok; } ST_FUNC void asm_expr(TCCState *s1, ExprValue *pe); static int tcc_assemble_internal(TCCState *s1, int do_preprocess, int global); static Sym sym_dot; /* Return a symbol we can use inside the assembler, having name NAME. The assembler symbol table is different from the C symbol table (and the Sym members are used differently). But we must be able to look up file-global C symbols from inside the assembler, e.g. for global asm blocks to be able to refer to defined C symbols. This routine gives back either an existing asm-internal symbol, or a new one. In the latter case the new asm-internal symbol is initialized with info from the C symbol table. If CSYM is non-null we take symbol info from it, otherwise we look up NAME in the C symbol table and use that. */ ST_FUNC Sym* get_asm_sym(int name, Sym *csym) { Sym *sym = label_find(name); if (!sym) { sym = label_push(&tcc_state->asm_labels, name, 0); sym->type.t = VT_VOID | VT_EXTERN; if (!csym) { csym = sym_find(name); /* We might be called for an asm block from inside a C routine and so might have local decls on the identifier stack. Search for the first global one. */ while (csym && csym->sym_scope) csym = csym->prev_tok; } /* Now, if we have a defined global symbol copy over section and offset. */ if (csym && ((csym->r & (VT_SYM|VT_CONST)) == (VT_SYM|VT_CONST)) && csym->c) { ElfW(Sym) *esym; esym = &((ElfW(Sym) *)symtab_section->data)[csym->c]; sym->c = csym->c; sym->r = esym->st_shndx; sym->jnext = esym->st_value; /* XXX can't yet store st_size anywhere. */ sym->type.t &= ~VT_EXTERN; /* Mark that this asm symbol doesn't need to be fed back. */ sym->a.dllimport = 1; } } return sym; } /* We do not use the C expression parser to handle symbols. Maybe the C expression parser could be tweaked to do so. */ static void asm_expr_unary(TCCState *s1, ExprValue *pe) { Sym *sym; int op, label; uint64_t n; const char *p; switch(tok) { case TOK_PPNUM: p = tokc.str.data; n = strtoull(p, (char **)&p, 0); if (*p == 'b' || *p == 'f') { /* backward or forward label */ label = asm_get_local_label_name(s1, n); sym = label_find(label); if (*p == 'b') { /* backward : find the last corresponding defined label */ if (sym && sym->r == 0) sym = sym->prev_tok; if (!sym) tcc_error("local label '%d' not found backward", n); } else { /* forward */ if (!sym || sym->r) { /* if the last label is defined, then define a new one */ sym = label_push(&s1->asm_labels, label, 0); sym->type.t = VT_STATIC | VT_VOID | VT_EXTERN; } } pe->v = 0; pe->sym = sym; pe->pcrel = 0; } else if (*p == '\0') { pe->v = n; pe->sym = NULL; pe->pcrel = 0; } else { tcc_error("invalid number syntax"); } next(); break; case '+': next(); asm_expr_unary(s1, pe); break; case '-': case '~': op = tok; next(); asm_expr_unary(s1, pe); if (pe->sym) tcc_error("invalid operation with label"); if (op == '-') pe->v = -pe->v; else pe->v = ~pe->v; break; case TOK_CCHAR: case TOK_LCHAR: pe->v = tokc.i; pe->sym = NULL; pe->pcrel = 0; next(); break; case '(': next(); asm_expr(s1, pe); skip(')'); break; case '.': pe->v = 0; pe->sym = &sym_dot; pe->pcrel = 0; sym_dot.type.t = VT_VOID | VT_STATIC; sym_dot.r = cur_text_section->sh_num; sym_dot.jnext = ind; next(); break; default: if (tok >= TOK_IDENT) { /* label case : if the label was not found, add one */ sym = get_asm_sym(tok, NULL); if (sym->r == SHN_ABS) { /* if absolute symbol, no need to put a symbol value */ pe->v = sym->jnext; pe->sym = NULL; pe->pcrel = 0; } else { pe->v = 0; pe->sym = sym; pe->pcrel = 0; } next(); } else { tcc_error("bad expression syntax [%s]", get_tok_str(tok, &tokc)); } break; } } static void asm_expr_prod(TCCState *s1, ExprValue *pe) { int op; ExprValue e2; asm_expr_unary(s1, pe); for(;;) { op = tok; if (op != '*' && op != '/' && op != '%' && op != TOK_SHL && op != TOK_SAR) break; next(); asm_expr_unary(s1, &e2); if (pe->sym || e2.sym) tcc_error("invalid operation with label"); switch(op) { case '*': pe->v *= e2.v; break; case '/': if (e2.v == 0) { div_error: tcc_error("division by zero"); } pe->v /= e2.v; break; case '%': if (e2.v == 0) goto div_error; pe->v %= e2.v; break; case TOK_SHL: pe->v <<= e2.v; break; default: case TOK_SAR: pe->v >>= e2.v; break; } } } static void asm_expr_logic(TCCState *s1, ExprValue *pe) { int op; ExprValue e2; asm_expr_prod(s1, pe); for(;;) { op = tok; if (op != '&' && op != '|' && op != '^') break; next(); asm_expr_prod(s1, &e2); if (pe->sym || e2.sym) tcc_error("invalid operation with label"); switch(op) { case '&': pe->v &= e2.v; break; case '|': pe->v |= e2.v; break; default: case '^': pe->v ^= e2.v; break; } } } static inline void asm_expr_sum(TCCState *s1, ExprValue *pe) { int op; ExprValue e2; asm_expr_logic(s1, pe); for(;;) { op = tok; if (op != '+' && op != '-') break; next(); asm_expr_logic(s1, &e2); if (op == '+') { if (pe->sym != NULL && e2.sym != NULL) goto cannot_relocate; pe->v += e2.v; if (pe->sym == NULL && e2.sym != NULL) pe->sym = e2.sym; } else { pe->v -= e2.v; /* NOTE: we are less powerful than gas in that case because we store only one symbol in the expression */ if (!e2.sym) { /* OK */ } else if (pe->sym == e2.sym) { /* OK */ pe->sym = NULL; /* same symbols can be subtracted to NULL */ } else if (pe->sym && pe->sym->r == e2.sym->r && pe->sym->r != 0) { /* we also accept defined symbols in the same section */ pe->v += pe->sym->jnext - e2.sym->jnext; pe->sym = NULL; } else if (e2.sym->r == cur_text_section->sh_num) { /* When subtracting a defined symbol in current section this actually makes the value PC-relative. */ pe->v -= e2.sym->jnext - ind - 4; pe->pcrel = 1; e2.sym = NULL; } else { cannot_relocate: tcc_error("invalid operation with label"); } } } } static inline void asm_expr_cmp(TCCState *s1, ExprValue *pe) { int op; ExprValue e2; asm_expr_sum(s1, pe); for(;;) { op = tok; if (op != TOK_EQ && op != TOK_NE && (op > TOK_GT || op < TOK_ULE)) break; next(); asm_expr_sum(s1, &e2); if (pe->sym || e2.sym) tcc_error("invalid operation with label"); switch(op) { case TOK_EQ: pe->v = pe->v == e2.v; break; case TOK_NE: pe->v = pe->v != e2.v; break; case TOK_LT: pe->v = (int64_t)pe->v < (int64_t)e2.v; break; case TOK_GE: pe->v = (int64_t)pe->v >= (int64_t)e2.v; break; case TOK_LE: pe->v = (int64_t)pe->v <= (int64_t)e2.v; break; case TOK_GT: pe->v = (int64_t)pe->v > (int64_t)e2.v; break; default: break; } /* GAS compare results are -1/0 not 1/0. */ pe->v = -(int64_t)pe->v; } } ST_FUNC void asm_expr(TCCState *s1, ExprValue *pe) { asm_expr_cmp(s1, pe); } ST_FUNC int asm_int_expr(TCCState *s1) { ExprValue e; asm_expr(s1, &e); if (e.sym) expect("constant"); return e.v; } /* NOTE: the same name space as C labels is used to avoid using too much memory when storing labels in TokenStrings */ static Sym* asm_new_label1(TCCState *s1, int label, int is_local, int sh_num, int value) { Sym *sym; sym = label_find(label); if (sym) { /* A VT_EXTERN symbol, even if it has a section is considered overridable. This is how we "define" .set targets. Real definitions won't have VT_EXTERN set. */ if (sym->r && !(sym->type.t & VT_EXTERN)) { /* the label is already defined */ if (!is_local) { tcc_error("assembler label '%s' already defined", get_tok_str(label, NULL)); } else { /* redefinition of local labels is possible */ goto new_label; } } } else { new_label: sym = label_push(&s1->asm_labels, label, 0); /* If we need a symbol to hold a value, mark it as tentative only (for .set). If this is for a real label we'll remove VT_EXTERN. */ sym->type.t = VT_STATIC | VT_VOID | VT_EXTERN; } sym->r = sh_num; sym->jnext = value; return sym; } static Sym* asm_new_label(TCCState *s1, int label, int is_local) { return asm_new_label1(s1, label, is_local, cur_text_section->sh_num, ind); } /* Set the value of LABEL to that of some expression (possibly involving other symbols). LABEL can be overwritten later still. */ static Sym* set_symbol(TCCState *s1, int label) { long n; ExprValue e; next(); asm_expr(s1, &e); n = e.v; if (e.sym) n += e.sym->jnext; return asm_new_label1(s1, label, 0, e.sym ? e.sym->r : SHN_ABS, n); } static void asm_free_labels(TCCState *st) { Sym *s, *s1; Section *sec; for(s = st->asm_labels; s != NULL; s = s1) { s1 = s->prev; /* define symbol value in object file */ s->type.t &= ~VT_EXTERN; if (s->r && !s->a.dllimport) { if (s->r == SHN_ABS) sec = SECTION_ABS; else sec = st->sections[s->r]; put_extern_sym2(s, sec, s->jnext, 0, 0); } /* remove label */ table_ident[s->v - TOK_IDENT]->sym_label = NULL; sym_free(s); } st->asm_labels = NULL; } static void use_section1(TCCState *s1, Section *sec) { cur_text_section->data_offset = ind; cur_text_section = sec; ind = cur_text_section->data_offset; } static void use_section(TCCState *s1, const char *name) { Section *sec; sec = find_section(s1, name); use_section1(s1, sec); } static void push_section(TCCState *s1, const char *name) { Section *sec = find_section(s1, name); sec->prev = cur_text_section; use_section1(s1, sec); } static void pop_section(TCCState *s1) { Section *prev = cur_text_section->prev; if (!prev) tcc_error(".popsection without .pushsection"); cur_text_section->prev = NULL; use_section1(s1, prev); } static void asm_parse_directive(TCCState *s1, int global) { int n, offset, v, size, tok1; Section *sec; uint8_t *ptr; /* assembler directive */ sec = cur_text_section; switch(tok) { case TOK_ASMDIR_align: case TOK_ASMDIR_balign: case TOK_ASMDIR_p2align: case TOK_ASMDIR_skip: case TOK_ASMDIR_space: tok1 = tok; next(); n = asm_int_expr(s1); if (tok1 == TOK_ASMDIR_p2align) { if (n < 0 || n > 30) tcc_error("invalid p2align, must be between 0 and 30"); n = 1 << n; tok1 = TOK_ASMDIR_align; } if (tok1 == TOK_ASMDIR_align || tok1 == TOK_ASMDIR_balign) { if (n < 0 || (n & (n-1)) != 0) tcc_error("alignment must be a positive power of two"); offset = (ind + n - 1) & -n; size = offset - ind; /* the section must have a compatible alignment */ if (sec->sh_addralign < n) sec->sh_addralign = n; } else { if (n < 0) n = 0; size = n; } v = 0; if (tok == ',') { next(); v = asm_int_expr(s1); } zero_pad: if (sec->sh_type != SHT_NOBITS) { sec->data_offset = ind; ptr = section_ptr_add(sec, size); memset(ptr, v, size); } ind += size; break; case TOK_ASMDIR_quad: #ifdef TCC_TARGET_X86_64 size = 8; goto asm_data; #else next(); for(;;) { uint64_t vl; const char *p; p = tokc.str.data; if (tok != TOK_PPNUM) { error_constant: tcc_error("64 bit constant"); } vl = strtoll(p, (char **)&p, 0); if (*p != '\0') goto error_constant; next(); if (sec->sh_type != SHT_NOBITS) { /* XXX: endianness */ gen_le32(vl); gen_le32(vl >> 32); } else { ind += 8; } if (tok != ',') break; next(); } break; #endif case TOK_ASMDIR_byte: size = 1; goto asm_data; case TOK_ASMDIR_word: case TOK_ASMDIR_short: size = 2; goto asm_data; case TOK_ASMDIR_long: case TOK_ASMDIR_int: size = 4; asm_data: next(); for(;;) { ExprValue e; asm_expr(s1, &e); if (sec->sh_type != SHT_NOBITS) { if (size == 4) { gen_expr32(&e); #ifdef TCC_TARGET_X86_64 } else if (size == 8) { gen_expr64(&e); #endif } else { if (e.sym) expect("constant"); if (size == 1) g(e.v); else gen_le16(e.v); } } else { ind += size; } if (tok != ',') break; next(); } break; case TOK_ASMDIR_fill: { int repeat, size, val, i, j; uint8_t repeat_buf[8]; next(); repeat = asm_int_expr(s1); if (repeat < 0) { tcc_error("repeat < 0; .fill ignored"); break; } size = 1; val = 0; if (tok == ',') { next(); size = asm_int_expr(s1); if (size < 0) { tcc_error("size < 0; .fill ignored"); break; } if (size > 8) size = 8; if (tok == ',') { next(); val = asm_int_expr(s1); } } /* XXX: endianness */ repeat_buf[0] = val; repeat_buf[1] = val >> 8; repeat_buf[2] = val >> 16; repeat_buf[3] = val >> 24; repeat_buf[4] = 0; repeat_buf[5] = 0; repeat_buf[6] = 0; repeat_buf[7] = 0; for(i = 0; i < repeat; i++) { for(j = 0; j < size; j++) { g(repeat_buf[j]); } } } break; case TOK_ASMDIR_rept: { int repeat; TokenString *init_str; ParseState saved_parse_state = {0}; next(); repeat = asm_int_expr(s1); init_str = tok_str_alloc(); next(); while ((tok != TOK_ASMDIR_endr) && (tok != CH_EOF)) { tok_str_add_tok(init_str); next(); } if (tok == CH_EOF) tcc_error("we at end of file, .endr not found"); next(); tok_str_add(init_str, -1); tok_str_add(init_str, 0); save_parse_state(&saved_parse_state); begin_macro(init_str, 1); while (repeat-- > 0) { tcc_assemble_internal(s1, (parse_flags & PARSE_FLAG_PREPROCESS), global); macro_ptr = init_str->str; } end_macro(); restore_parse_state(&saved_parse_state); break; } case TOK_ASMDIR_org: { unsigned long n; ExprValue e; next(); asm_expr(s1, &e); n = e.v; if (e.sym) { if (e.sym->r != cur_text_section->sh_num) expect("constant or same-section symbol"); n += e.sym->jnext; } if (n < ind) tcc_error("attempt to .org backwards"); v = 0; size = n - ind; goto zero_pad; } break; case TOK_ASMDIR_set: next(); tok1 = tok; next(); /* Also accept '.set stuff', but don't do anything with this. It's used in GAS to set various features like '.set mips16'. */ if (tok == ',') set_symbol(s1, tok1); break; case TOK_ASMDIR_globl: case TOK_ASMDIR_global: case TOK_ASMDIR_weak: case TOK_ASMDIR_hidden: tok1 = tok; do { Sym *sym; next(); sym = get_asm_sym(tok, NULL); if (tok1 != TOK_ASMDIR_hidden) sym->type.t &= ~VT_STATIC; if (tok1 == TOK_ASMDIR_weak) sym->a.weak = 1; else if (tok1 == TOK_ASMDIR_hidden) sym->a.visibility = STV_HIDDEN; next(); } while (tok == ','); break; case TOK_ASMDIR_string: case TOK_ASMDIR_ascii: case TOK_ASMDIR_asciz: { const uint8_t *p; int i, size, t; t = tok; next(); for(;;) { if (tok != TOK_STR) expect("string constant"); p = tokc.str.data; size = tokc.str.size; if (t == TOK_ASMDIR_ascii && size > 0) size--; for(i = 0; i < size; i++) g(p[i]); next(); if (tok == ',') { next(); } else if (tok != TOK_STR) { break; } } } break; case TOK_ASMDIR_text: case TOK_ASMDIR_data: case TOK_ASMDIR_bss: { char sname[64]; tok1 = tok; n = 0; next(); if (tok != ';' && tok != TOK_LINEFEED) { n = asm_int_expr(s1); next(); } if (n) sprintf(sname, "%s%d", get_tok_str(tok1, NULL), n); else sprintf(sname, "%s", get_tok_str(tok1, NULL)); use_section(s1, sname); } break; case TOK_ASMDIR_file: { char filename[512]; filename[0] = '\0'; next(); if (tok == TOK_STR) pstrcat(filename, sizeof(filename), tokc.str.data); else pstrcat(filename, sizeof(filename), get_tok_str(tok, NULL)); if (s1->warn_unsupported) tcc_warning("ignoring .file %s", filename); next(); } break; case TOK_ASMDIR_ident: { char ident[256]; ident[0] = '\0'; next(); if (tok == TOK_STR) pstrcat(ident, sizeof(ident), tokc.str.data); else pstrcat(ident, sizeof(ident), get_tok_str(tok, NULL)); if (s1->warn_unsupported) tcc_warning("ignoring .ident %s", ident); next(); } break; case TOK_ASMDIR_size: { Sym *sym; next(); sym = label_find(tok); if (!sym) { tcc_error("label not found: %s", get_tok_str(tok, NULL)); } /* XXX .size name,label2-label1 */ if (s1->warn_unsupported) tcc_warning("ignoring .size %s,*", get_tok_str(tok, NULL)); next(); skip(','); while (tok != TOK_LINEFEED && tok != ';' && tok != CH_EOF) { next(); } } break; case TOK_ASMDIR_type: { Sym *sym; const char *newtype; next(); sym = get_asm_sym(tok, NULL); next(); skip(','); if (tok == TOK_STR) { newtype = tokc.str.data; } else { if (tok == '@' || tok == '%') next(); newtype = get_tok_str(tok, NULL); } if (!strcmp(newtype, "function") || !strcmp(newtype, "STT_FUNC")) { sym->type.t = (sym->type.t & ~VT_BTYPE) | VT_FUNC; } else if (s1->warn_unsupported) tcc_warning("change type of '%s' from 0x%x to '%s' ignored", get_tok_str(sym->v, NULL), sym->type.t, newtype); next(); } break; case TOK_ASMDIR_pushsection: case TOK_ASMDIR_section: { char sname[256]; int old_nb_section = s1->nb_sections; tok1 = tok; /* XXX: support more options */ next(); sname[0] = '\0'; while (tok != ';' && tok != TOK_LINEFEED && tok != ',') { if (tok == TOK_STR) pstrcat(sname, sizeof(sname), tokc.str.data); else pstrcat(sname, sizeof(sname), get_tok_str(tok, NULL)); next(); } if (tok == ',') { /* skip section options */ next(); if (tok != TOK_STR) expect("string constant"); next(); if (tok == ',') { next(); if (tok == '@' || tok == '%') next(); next(); } } last_text_section = cur_text_section; if (tok1 == TOK_ASMDIR_section) use_section(s1, sname); else push_section(s1, sname); /* If we just allocated a new section reset its alignment to 1. new_section normally acts for GCC compatibility and sets alignment to PTR_SIZE. The assembler behaves different. */ if (old_nb_section != s1->nb_sections) cur_text_section->sh_addralign = 1; } break; case TOK_ASMDIR_previous: { Section *sec; next(); if (!last_text_section) tcc_error("no previous section referenced"); sec = cur_text_section; use_section1(s1, last_text_section); last_text_section = sec; } break; case TOK_ASMDIR_popsection: next(); pop_section(s1); break; #ifdef TCC_TARGET_I386 case TOK_ASMDIR_code16: { next(); s1->seg_size = 16; } break; case TOK_ASMDIR_code32: { next(); s1->seg_size = 32; } break; #endif #ifdef TCC_TARGET_X86_64 /* added for compatibility with GAS */ case TOK_ASMDIR_code64: next(); break; #endif default: tcc_error("unknown assembler directive '.%s'", get_tok_str(tok, NULL)); break; } } /* assemble a file */ static int tcc_assemble_internal(TCCState *s1, int do_preprocess, int global) { int opcode; /* XXX: undefine C labels */ ch = file->buf_ptr[0]; tok_flags = TOK_FLAG_BOL | TOK_FLAG_BOF; parse_flags = PARSE_FLAG_ASM_FILE | PARSE_FLAG_TOK_STR; set_idnum('.', IS_ID); if (do_preprocess) parse_flags |= PARSE_FLAG_PREPROCESS; for(;;) { next(); if (tok == TOK_EOF) break; /* generate line number info */ if (global && s1->do_debug) tcc_debug_line(s1); parse_flags |= PARSE_FLAG_LINEFEED; /* XXX: suppress that hack */ redo: if (tok == '#') { /* horrible gas comment */ while (tok != TOK_LINEFEED) next(); } else if (tok >= TOK_ASMDIR_FIRST && tok <= TOK_ASMDIR_LAST) { asm_parse_directive(s1, global); } else if (tok == TOK_PPNUM) { Sym *sym; const char *p; int n; p = tokc.str.data; n = strtoul(p, (char **)&p, 10); if (*p != '\0') expect("':'"); /* new local label */ sym = asm_new_label(s1, asm_get_local_label_name(s1, n), 1); /* Remove the marker for tentative definitions. */ sym->type.t &= ~VT_EXTERN; next(); skip(':'); goto redo; } else if (tok >= TOK_IDENT) { /* instruction or label */ opcode = tok; next(); if (tok == ':') { /* handle "extern void vide(void); __asm__("vide: ret");" as "__asm__("globl vide\nvide: ret");" */ Sym *sym = sym_find(opcode); if (sym && (sym->type.t & VT_EXTERN) && global) { sym = label_find(opcode); if (!sym) { sym = label_push(&s1->asm_labels, opcode, 0); sym->type.t = VT_VOID | VT_EXTERN; } } /* new label */ sym = asm_new_label(s1, opcode, 0); sym->type.t &= ~VT_EXTERN; next(); goto redo; } else if (tok == '=') { set_symbol(s1, opcode); goto redo; } else { asm_opcode(s1, opcode); } } /* end of line */ if (tok != ';' && tok != TOK_LINEFEED) expect("end of line"); parse_flags &= ~PARSE_FLAG_LINEFEED; /* XXX: suppress that hack */ } asm_free_labels(s1); return 0; } /* Assemble the current file */ ST_FUNC int tcc_assemble(TCCState *s1, int do_preprocess) { Sym *define_start; int ret; define_start = define_stack; preprocess_start(s1); tcc_debug_start(s1); /* default section is text */ cur_text_section = text_section; ind = cur_text_section->data_offset; nocode_wanted = 0; ret = tcc_assemble_internal(s1, do_preprocess, 1); cur_text_section->data_offset = ind; tcc_debug_end(s1); free_defines(define_start); return ret; } /********************************************************************/ /* GCC inline asm support */ /* assemble the string 'str' in the current C compilation unit without C preprocessing. NOTE: str is modified by modifying the '\0' at the end */ static void tcc_assemble_inline(TCCState *s1, char *str, int len, int global) { int saved_parse_flags; const int *saved_macro_ptr; saved_parse_flags = parse_flags; saved_macro_ptr = macro_ptr; tcc_open_bf(s1, ":asm:", len); memcpy(file->buffer, str, len); macro_ptr = NULL; tcc_assemble_internal(s1, 0, global); tcc_close(); parse_flags = saved_parse_flags; set_idnum('.', (parse_flags & PARSE_FLAG_ASM_FILE) ? IS_ID : 0); macro_ptr = saved_macro_ptr; } /* find a constraint by its number or id (gcc 3 extended syntax). return -1 if not found. Return in *pp in char after the constraint */ ST_FUNC int find_constraint(ASMOperand *operands, int nb_operands, const char *name, const char **pp) { int index; TokenSym *ts; const char *p; if (isnum(*name)) { index = 0; while (isnum(*name)) { index = (index * 10) + (*name) - '0'; name++; } if ((unsigned)index >= nb_operands) index = -1; } else if (*name == '[') { name++; p = strchr(name, ']'); if (p) { ts = tok_alloc(name, p - name); for(index = 0; index < nb_operands; index++) { if (operands[index].id == ts->tok) goto found; } index = -1; found: name = p + 1; } else { index = -1; } } else { index = -1; } if (pp) *pp = name; return index; } static void subst_asm_operands(ASMOperand *operands, int nb_operands, CString *out_str, CString *in_str) { int c, index, modifier; const char *str; ASMOperand *op; SValue sv; cstr_new(out_str); str = in_str->data; for(;;) { c = *str++; if (c == '%') { if (*str == '%') { str++; goto add_char; } modifier = 0; if (*str == 'c' || *str == 'n' || *str == 'b' || *str == 'w' || *str == 'h' || *str == 'k' || *str == 'q' || /* P in GCC would add "@PLT" to symbol refs in PIC mode, and make literal operands not be decorated with '$'. */ *str == 'P') modifier = *str++; index = find_constraint(operands, nb_operands, str, &str); if (index < 0) tcc_error("invalid operand reference after %%"); op = &operands[index]; sv = *op->vt; if (op->reg >= 0) { sv.r = op->reg; if ((op->vt->r & VT_VALMASK) == VT_LLOCAL && op->is_memory) sv.r |= VT_LVAL; } subst_asm_operand(out_str, &sv, modifier); } else { add_char: cstr_ccat(out_str, c); if (c == '\0') break; } } } static void parse_asm_operands(ASMOperand *operands, int *nb_operands_ptr, int is_output) { ASMOperand *op; int nb_operands; if (tok != ':') { nb_operands = *nb_operands_ptr; for(;;) { CString astr; if (nb_operands >= MAX_ASM_OPERANDS) tcc_error("too many asm operands"); op = &operands[nb_operands++]; op->id = 0; if (tok == '[') { next(); if (tok < TOK_IDENT) expect("identifier"); op->id = tok; next(); skip(']'); } parse_mult_str(&astr, "string constant"); op->constraint = tcc_malloc(astr.size); strcpy(op->constraint, astr.data); cstr_free(&astr); skip('('); gexpr(); if (is_output) { if (!(vtop->type.t & VT_ARRAY)) test_lvalue(); } else { /* we want to avoid LLOCAL case, except when the 'm' constraint is used. Note that it may come from register storage, so we need to convert (reg) case */ if ((vtop->r & VT_LVAL) && ((vtop->r & VT_VALMASK) == VT_LLOCAL || (vtop->r & VT_VALMASK) < VT_CONST) && !strchr(op->constraint, 'm')) { gv(RC_INT); } } op->vt = vtop; skip(')'); if (tok == ',') { next(); } else { break; } } *nb_operands_ptr = nb_operands; } } /* parse the GCC asm() instruction */ ST_FUNC void asm_instr(void) { CString astr, astr1; ASMOperand operands[MAX_ASM_OPERANDS]; int nb_outputs, nb_operands, i, must_subst, out_reg; uint8_t clobber_regs[NB_ASM_REGS]; next(); /* since we always generate the asm() instruction, we can ignore volatile */ if (tok == TOK_VOLATILE1 || tok == TOK_VOLATILE2 || tok == TOK_VOLATILE3) { next(); } parse_asm_str(&astr); nb_operands = 0; nb_outputs = 0; must_subst = 0; memset(clobber_regs, 0, sizeof(clobber_regs)); if (tok == ':') { next(); must_subst = 1; /* output args */ parse_asm_operands(operands, &nb_operands, 1); nb_outputs = nb_operands; if (tok == ':') { next(); if (tok != ')') { /* input args */ parse_asm_operands(operands, &nb_operands, 0); if (tok == ':') { /* clobber list */ /* XXX: handle registers */ next(); for(;;) { if (tok != TOK_STR) expect("string constant"); asm_clobber(clobber_regs, tokc.str.data); next(); if (tok == ',') { next(); } else { break; } } } } } } skip(')'); /* NOTE: we do not eat the ';' so that we can restore the current token after the assembler parsing */ if (tok != ';') expect("';'"); /* save all values in the memory */ save_regs(0); /* compute constraints */ asm_compute_constraints(operands, nb_operands, nb_outputs, clobber_regs, &out_reg); /* substitute the operands in the asm string. No substitution is done if no operands (GCC behaviour) */ #ifdef ASM_DEBUG printf("asm: \"%s\"\n", (char *)astr.data); #endif if (must_subst) { subst_asm_operands(operands, nb_operands, &astr1, &astr); cstr_free(&astr); } else { astr1 = astr; } #ifdef ASM_DEBUG printf("subst_asm: \"%s\"\n", (char *)astr1.data); #endif /* generate loads */ asm_gen_code(operands, nb_operands, nb_outputs, 0, clobber_regs, out_reg); /* assemble the string with tcc internal assembler */ tcc_assemble_inline(tcc_state, astr1.data, astr1.size - 1, 0); /* restore the current C token */ next(); /* store the output values if needed */ asm_gen_code(operands, nb_operands, nb_outputs, 1, clobber_regs, out_reg); /* free everything */ for(i=0;iconstraint); vpop(); } cstr_free(&astr1); } ST_FUNC void asm_global_instr(void) { CString astr; int saved_nocode_wanted = nocode_wanted; /* Global asm blocks are always emitted. */ nocode_wanted = 0; next(); parse_asm_str(&astr); skip(')'); /* NOTE: we do not eat the ';' so that we can restore the current token after the assembler parsing */ if (tok != ';') expect("';'"); #ifdef ASM_DEBUG printf("asm_global: \"%s\"\n", (char *)astr.data); #endif cur_text_section = text_section; ind = cur_text_section->data_offset; /* assemble the string with tcc internal assembler */ tcc_assemble_inline(tcc_state, astr.data, astr.size - 1, 1); cur_text_section->data_offset = ind; /* restore the current C token */ next(); cstr_free(&astr); nocode_wanted = saved_nocode_wanted; } #endif /* CONFIG_TCC_ASM */