mirror of
https://github.com/mirror/tinycc.git
synced 2024-12-28 04:00:06 +08:00
5bd8aeb917
This happens when e.g. string constants (or other static data) are passed as operands to inline asm as immediates. The produced symbol ref wouldn't be found. So tighten the connection between C and asm-local symbol table even more.
1323 lines
36 KiB
C
1323 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);
|
|
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->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->type.t |= VT_IMPORT;
|
|
}
|
|
}
|
|
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;
|
|
unsigned long n;
|
|
const char *p;
|
|
|
|
switch(tok) {
|
|
case TOK_PPNUM:
|
|
p = tokc.str.data;
|
|
n = strtoul(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->type.t & VT_IMPORT)) {
|
|
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 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));
|
|
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->type.t |= VT_WEAK;
|
|
else if (tok1 == TOK_ASMDIR_hidden)
|
|
sym->type.t |= STV_HIDDEN << VT_VIS_SHIFT;
|
|
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 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;
|
|
next();
|
|
for(;;) {
|
|
if (tok == TOK_EOF)
|
|
break;
|
|
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);
|
|
} 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) && nocode_wanted) {
|
|
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 */
|
|
next();
|
|
}
|
|
|
|
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;
|
|
|
|
preprocess_start(s1);
|
|
|
|
/* default section is text */
|
|
cur_text_section = text_section;
|
|
ind = cur_text_section->data_offset;
|
|
|
|
define_start = define_stack;
|
|
|
|
/* an elf symbol of type STT_FILE must be put so that STB_LOCAL
|
|
symbols can be safely used */
|
|
put_elf_sym(symtab_section, 0, 0,
|
|
ELFW(ST_INFO)(STB_LOCAL, STT_FILE), 0,
|
|
SHN_ABS, file->filename);
|
|
|
|
ret = tcc_assemble_internal(s1, do_preprocess);
|
|
|
|
cur_text_section->data_offset = ind;
|
|
|
|
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 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);
|
|
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,
|
|
int nb_outputs,
|
|
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, nb_outputs, &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);
|
|
|
|
/* 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;
|
|
|
|
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);
|
|
|
|
cur_text_section->data_offset = ind;
|
|
|
|
/* restore the current C token */
|
|
next();
|
|
|
|
cstr_free(&astr);
|
|
}
|
|
#endif /* CONFIG_TCC_ASM */
|