tinycc/tccasm.c

1322 lines
36 KiB
C

/*
* 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;i<nb_operands;i++) {
ASMOperand *op;
op = &operands[i];
tcc_free(op->constraint);
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 */