/*
* Copyright (c) 2006 - 2007, Nils R. Weller
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* Parses gas inline assembly language statements and converts them to NASM
* code
*/
#include "inlineasm.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
#include "defs.h"
#include "token.h"
#include "expr.h"
#include "backend.h"
#include "error.h"
#include "x86_emit_nasm.h"
#include "x86_emit_gas.h"
#include "x86_gen.h"
#include "n_libc.h"
static int lineno;
static struct token *asm_tok;
/*
* XXX the store_char() stuff bothers me, this adhoc interface should've been
* trashed long ago
*/
static int
get_char(char **code) {
int ret;
if (**code == 0) {
return EOF;
}
ret = (unsigned char)**code;
++*code;
return ret;
}
static void
unget_char(int ch, char **code) {
(void) ch;
--*code;
}
#if 0
struct gas_token {
int type;
#define GAS_SEPARATOR 1001
#define GAS_DOTSTRING 1002
#define GAS_STRING 1003
#define GAS_DOLLAR 1004
#define GAS_OCTAL 1020
#define GAS_HEXA 1021
#define GAS_DECIMAL 1022
#define GAS_NUMBER(x) (x == GAS_OCTAL || x == GAS_HEXA || x == GAS_DECIMAL)
int lineno;
int idata;
void *data;
char *ascii;
struct gas_token *next;
};
#endif
#define ALLOCATED_TOKEN(type) \
(type == GAS_DOTIDENT || type == GAS_IDENT || \
type == GAS_OCTAL || type == GAS_HEXA || type == GAS_DECIMAL)
static struct gas_token *
make_gas_token(int type, void *data) {
struct gas_token *ret;
static struct gas_token nulltok;
ret = n_xmalloc(sizeof *ret);
*ret = nulltok;
ret->type = type;
ret->data = data;
ret->lineno = lineno;
switch (ret->type) {
case TOK_PAREN_OPEN:
ret->ascii = "(";
break;
case TOK_PAREN_CLOSE:
ret->ascii = ")";
break;
case GAS_IDENT:
case GAS_DOTIDENT:
case GAS_OCTAL:
case GAS_HEXA:
case GAS_DECIMAL:
default:
ret->ascii = ret->data;
break;
}
return ret;
}
static int gas_errors;
static void
gas_errorfl(struct gas_token *gt, const char *fmt, ...) {
va_list va;
++gas_errors;
++errors;
fprintf(stderr, "%s:%d+%d: ", asm_tok->file, asm_tok->line, gt->lineno);
va_start(va, fmt);
vfprintf(stderr, fmt, va);
va_end(va);
fputc('\n', stderr);
print_source_line(asm_tok->line_ptr, asm_tok->tok_ptr, asm_tok->ascii,
1);
}
static struct gas_token *
get_string(char **code) {
char *buf = NULL;
int ch;
store_char(NULL, 0);
while ((ch = get_char(code)) != EOF) {
if (ch == '"') {
break;
}
store_char(&buf, ch);
}
store_char(&buf, 0);
return make_gas_token(GAS_STRING, buf);
}
static struct gas_token *
get_ident(int firstch, char **code) {
char *buf = NULL;
int toktype;
int ch;
store_char(NULL, 0);
if (firstch == '.') {
toktype = GAS_DOTIDENT;
} else {
toktype = GAS_IDENT;
store_char(&buf, firstch);
}
while ((ch = get_char(code)) != EOF) {
if (isalnum(ch) || ch == '_') {
store_char(&buf, ch);
} else {
unget_char(ch, code);
break;
}
}
store_char(&buf, 0);
return make_gas_token(toktype, buf);
}
static struct gas_token *
get_number(int firstch, char **code, int sign) {
char *buf = NULL;
struct gas_token *ret;
unsigned long *val;
int hexa = 0;
int octal = 0;
int ch;
store_char(NULL, 0);
store_char(&buf, firstch);
if (firstch == '0') {
/* May be hexadecimal */
if ((ch = get_char(code)) == EOF) {
goto out;
}
if (ch == 'x') {
store_char(&buf, 'x');
hexa = 1;
} else if (!isdigit(ch)) {
store_char(&buf, '0');
unget_char(ch, code);
goto out;
} else {
octal = 1;
store_char(&buf, ch);
}
}
while ((ch = get_char(code)) != EOF) {
ch = tolower(ch);
if (isdigit(ch)
|| (hexa && ch && strchr("abcdef", ch))) {
store_char(&buf, ch);
} else {
unget_char(ch, code);
break;
}
}
out:
store_char(&buf, 0);
val = n_xmalloc(sizeof *val);
if (octal) {
sscanf(buf, "%lo", val);
ret = make_gas_token(GAS_OCTAL, buf);
} else if (hexa) {
sscanf(buf, "%lx", val);
ret = make_gas_token(GAS_HEXA, buf);
} else {
sscanf(buf, "%ld", val);
ret = make_gas_token(GAS_DECIMAL, buf);
}
ret->data2 = val;
if (sign) {
ret->idata = 1;
}
return ret;
}
static void
append_gas_list(
struct gas_token **dest,
struct gas_token **dest_tail,
struct gas_token *src) {
if (*dest == NULL) {
*dest = *dest_tail = src;
} else {
(*dest_tail)->next = src;
*dest_tail = (*dest_tail)->next;
}
}
static void
free_gas_token(struct gas_token *t) {
if (t == NULL) return;
if (t->data != NULL) {
if (ALLOCATED_TOKEN(t->type)) {
free(t->data);
}
}
free(t);
}
static void
free_gas_token_list(struct gas_token *t) {
while (t != NULL) {
struct gas_token *next = t->next;
free_gas_token(t);
t = next;
}
}
static struct gas_token *
tokenize_gas_code(char *code) {
int ch;
struct gas_token *gt;
struct gas_token *glist = NULL;
struct gas_token *glist_tail = NULL;
struct gas_token dummytok;
lineno = 0;
while ((ch = get_char(&code)) != EOF) {
gt = NULL;
switch (ch) {
case '\t':
case ' ':
case '\n':
if (ch == '\n') {
++lineno;
gt = make_gas_token(GAS_SEPARATOR, NULL);
}
break;
case ';':
gt = make_gas_token(GAS_SEPARATOR, NULL);
break;
case '(':
gt = make_gas_token(TOK_PAREN_OPEN, NULL);
break;
case ')':
gt = make_gas_token(TOK_PAREN_CLOSE, NULL);
break;
case '-':
while ((ch = get_char(&code)) != EOF
&& isspace(ch))
;
if (ch == EOF || !isdigit(ch)) {
dummytok.lineno = lineno;
gas_errorfl(&dummytok,
"Parse error at `%c' (%d)",
ch == EOF? '-': ch, __LINE__);
free_gas_token_list(glist);
return NULL;
}
gt = get_number(ch, &code, 1);
break;
case ',':
gt = make_gas_token(TOK_OP_COMMA, NULL);
break;
case '"':
gt = get_string(&code);
break;
case ':':
gt = make_gas_token(TOK_OP_AMB_COND2, ":");
break;
case '$':
gt = make_gas_token(GAS_DOLLAR, "$");
break;
case '%':
gt = make_gas_token(TOK_OP_MOD, "%");
break;
case '.':
#if 0
if ((ch = get_char(&code)) == EOF
|| !isalnum((unsigned char)ch)) {
dummytok.lineno = lineno;
gas_errorfl(&dummytok,
"Parse error at `%c' (%d)",
ch, __LINE__);
free_gas_token_list(glist);
return NULL;
}
#endif
gt = get_ident(ch, &code);
break;
default:
if (isdigit((unsigned char)ch)) {
gt = get_number(ch, &code, 0);
} else if (isalpha((unsigned char)ch) || ch == '_') {
gt = get_ident(ch, &code);
} else {
dummytok.lineno = lineno;
gas_errorfl(&dummytok,
"Parse error at `%c' (%d)",
ch, __LINE__);
free_gas_token_list(glist);
return NULL;
}
}
if (gt != NULL) {
append_gas_list(&glist, &glist_tail, gt);
}
}
#if 0
printf("tokens:\n");
for (gt = glist; gt != NULL; gt = gt->next) {
printf("\t%d\n", gt->type);
}
#endif
return glist;
}
#if 0
struct gas_operand {
#define ITEM_REG 1
#define ITEM_NUMBER 2
#define ITEM_VARIABLE 3
#define ITEM_IO 4
int addr_mode;
#define ADDR_ABSOLUTE 1
#define ADDR_INDIRECT 2
#define ADDR_SCALED 3
#define ADDR_DISPLACE 4
#define ADDR_SCALED_DISPLACE 5
void *items[4];
int item_types[4];
};
#endif
static int
next_gas_token(struct gas_token **tok) {
if ((*tok)->next == NULL) {
struct gas_token dummy;
dummy.lineno = lineno;
gas_errorfl(&dummy, "Unexpected end of assembler statement");
return -1;
}
*tok = (*tok)->next;
return 0;
}
static int
is_number(const char *str) {
for (; isdigit((unsigned char)*str); ++str);
;
return *str == 0;
}
static struct inline_asm_io *
get_operand_by_idx(
struct inline_asm_stmt *st,
struct gas_token *t,
int idx,
int *curitem_type) {
struct inline_asm_io *io;
int n_ios = st->n_inputs + st->n_outputs;
if (t != NULL) {
idx = (int)*(unsigned long *)t->data2;
}
if (idx >= n_ios) {
gas_errorfl(t, "I/O operand %d does "
"not exist, highest is %lu "
"(first one is %0!)", idx, n_ios);
return NULL;
}
++idx;
if ((st->output != NULL
&& idx > st->n_outputs)
|| (st->output == NULL)) {
/* Must be input */
idx -= st->n_outputs;
*curitem_type = ITEM_INPUT;
io = st->input;
} else {
/* Output */
*curitem_type = ITEM_OUTPUT;
io = st->output;
}
while (--idx > 0) {
io = io->next;
}
return io;
}
static char *
get_constraint_by_idx(struct inline_asm_stmt *data, int idx) {
int i;
struct inline_asm_io *io = data->output;
int input_done = 0;
for (i = 0; i < idx; ++i) {
if (io == NULL) {
if (input_done) {
return NULL;
} else {
io = data->input;
}
}
if (io == NULL) {
return NULL;
}
io = io->next;
}
if (io != NULL) {
return io->constraints;
}
return NULL;
}
/*
* Parses an operand to an instruction. This resembles a state machine,
* which is why it is hard to read. Basically, it just always reads either
* a parentheses or an ``item'' (a register, number or variable.)
* Parentheses may dictate some sort of indirect addressing mode. The items
* and their types are stored in the items and item_types arrays in the
* order in which they appear;
* -0x20(%eax) -> -0x20=items[0], %eax=items[1]
* 0x80(%eax,%ebx,0x4) -> 0x80=items[0], %eax=items[1], ....
*/
static struct gas_operand *
parse_operand(struct gas_token **tok, struct inline_asm_stmt *stmt) {
struct gas_token *t;
struct gas_operand *ret;
static struct gas_operand nullop;
void **curitem;
int *curitem_type;
int index = 0;
ret = n_xmalloc(sizeof *ret);
*ret = nullop;
curitem = &ret->items[0];
curitem_type = &ret->item_types[0];
for (t = *tok; t != NULL; t = t->next) {
if (t->type == GAS_DOLLAR) {
/* Should be immediate - $1234 */
if (next_gas_token(&t) != 0) {
return NULL;
}
if (t->type == GAS_IDENT) {
*curitem_type = ITEM_VARIABLE;
*curitem = t;
} else if (GAS_NUMBER(t->type)) {
*curitem_type = ITEM_NUMBER;
*curitem = t;
} else {
gas_errorfl(t, "Parse error at `%s' (%d)",
t->ascii, __LINE__);
return NULL;
}
} else if (GAS_NUMBER(t->type)) {
if (t->next
&& t->next->type == GAS_IDENT
&& strcmp(t->next->data, "b") == 0) {
*curitem_type = ITEM_LABEL;
*curitem = /* XXX */
backend->get_inlineasm_label(t->data);
t = t->next;
} else {
*curitem_type = ITEM_NUMBER;
*curitem = t;
if (ret->addr_mode == 0) {
ret->addr_mode = ADDR_ABSOLUTE;
} else {
gas_errorfl(t,
"Parse error at `%s' (%d)",
t->ascii, __LINE__);
return NULL;
}
}
++index;
curitem = &ret->items[index];
curitem_type = &ret->item_types[index];
} else if (t->type == GAS_IDENT) {
/*
* XXX can this possibly be right, and made work with
* NASM?!?
*/
*curitem_type = ITEM_VARIABLE;
*curitem = t;
if (ret->addr_mode == 0) {
ret->addr_mode = ADDR_INDIRECT;
} else {
gas_errorfl(t, "Parse error at `%s' (%d)",
t->ascii, __LINE__);
return NULL;
}
} else if (t->type == TOK_OP_MOD) {
if (next_gas_token(&t) != 0) {
return NULL;
}
if (t->type == TOK_OP_MOD) {
/* Should be %%reg */
if (next_gas_token(&t) != 0) {
return NULL;
}
if (t->type != GAS_IDENT) {
gas_errorfl(t,
"Parse error at `%s' (%d)",
t->ascii, __LINE__);
return NULL;
} else if (!stmt->extended) {
gas_errorfl(t, "Second %% in %%%%reg "
"is only allowed for "
"extened asm statements "
"(use %reg instead)");
return NULL;
}
*curitem_type = ITEM_REG;
*curitem = t;
} else if (GAS_NUMBER(t->type)) {
struct inline_asm_io *io;
/* i/o operand */
if (!stmt->extended) {
gas_errorfl(t, "`%%number' is only "
"allowed in extended asm "
"statements");
return NULL;
}
io = get_operand_by_idx(stmt, t, 0,
curitem_type);
if (io == NULL) {
return NULL;
}
*curitem = io;
++index;
curitem = &ret->items[index];
curitem_type = &ret->item_types[index];
} else if (t->type == GAS_IDENT) {
if (stmt->extended) {
if ((t->ascii[0] == 'h'
|| t->ascii[0] == 'b')
&& is_number(t->ascii+1)) {
char *p;
long rc;
struct inline_asm_io *io;
errno = 0;
rc = strtol(t->ascii+1, &p, 0);
if (errno
|| rc == LONG_MIN
|| rc == LONG_MAX
|| *p) {
gas_errorfl(t,
"Parse error at `%s' (%d)",
t->ascii, __LINE__);
return NULL;
}
io = get_operand_by_idx(stmt,
NULL, rc, curitem_type);
if (io == NULL) {
return NULL;
}
*curitem = io;
*curitem_type =
t->ascii[0] == 'b'?
ITEM_SUBREG_B
: ITEM_SUBREG_H;
} else {
gas_errorfl(t, "%%reg isn't "
"allowed in extended asm statements (use %%%%reg instead)");
return NULL;
}
} else {
*curitem_type = ITEM_REG;
*curitem = t;
}
++index;
curitem = &ret->items[index];
curitem_type = &ret->item_types[index];
} else {
gas_errorfl(t, "Parse error at `%s' (%d)",
t->ascii, __LINE__);
return NULL;
}
} else if (t->type == TOK_PAREN_OPEN) {
if (t == *tok) {
/* Scaled or indirect addressing */
ret->addr_mode = ADDR_INDIRECT;
} else if (ret->item_types[0] != 0) {
/*
* Displacement - $123(stuff)
*/
if (ret->item_types[0] != ITEM_NUMBER) {
gas_errorfl(t, "Invalid "
"displacement");
return NULL;
}
ret->addr_mode = ADDR_DISPLACE;
curitem = &ret->items[1];
curitem_type = &ret->item_types[1];
++index;
} else {
gas_errorfl(t, "Parse error at `%s' (%d)",
t->ascii, __LINE__);
return NULL;
}
} else if (t->type == TOK_PAREN_CLOSE) {
if (ret->addr_mode != ADDR_INDIRECT
&& ret->addr_mode != ADDR_DISPLACE
&& ret->addr_mode != ADDR_SCALED
&& ret->addr_mode != ADDR_SCALED_DISPLACE) {
gas_errorfl(t, "Parse error at `%s' (%d)",
t->ascii, __LINE__);
return NULL;
}
if (t->next) {
if (t->next->type == TOK_OP_COMMA
|| t->next->type == GAS_SEPARATOR) {
t = t->next;
break;
} else {
/* XXX wut to do?! */
}
} else {
t = t->next;
break;
}
} else if (t->type == TOK_OP_COMMA) {
if (ret->addr_mode != ADDR_INDIRECT
&& ret->addr_mode != ADDR_SCALED
&& ret->addr_mode != ADDR_DISPLACE
&& ret->addr_mode != ADDR_SCALED_DISPLACE) {
/* ok, operand ends here */
break;
} else if (ret->addr_mode == ADDR_SCALED
|| ret->addr_mode == ADDR_SCALED_DISPLACE) {
int max = ret->addr_mode == ADDR_SCALED?
2: 3;
if (index >= max) {
gas_errorfl(t, "Too many operands"
" for scaled addressing");
} else {
curitem = &ret->items[++index];
curitem_type = &ret->item_types[index];
}
} else {
if (ret->addr_mode == ADDR_DISPLACE) {
/* 0x123(foo,bar,baz) */
ret->addr_mode = ADDR_SCALED_DISPLACE;
} else {
ret->addr_mode = ADDR_SCALED;
}
++index;
curitem = &ret->items[index];
curitem_type = &ret->item_types[index];
}
} else if (t->type == GAS_SEPARATOR) {
/* Instruction ends here */
break;
} else {
gas_errorfl(t, "Parse error at `%s' (%d)",
t->ascii, __LINE__);
return NULL;
}
}
*tok = t;
return ret;
}
#if 0
static void
free_gas_operand(struct gas_operand *t) {
(void)t;
}
#endif
static void
store_plusminus_num(FILE *out, void *item) {
struct gas_token *nt;
x_fputc(' ', out);
nt = item;
if (!nt->idata) {
/* Not signed */
x_fputc('+', out);
} else {
x_fputc('-', out);
}
x_fputc(' ', out);
print_item_nasm(out, item, ITEM_NUMBER, 0);
}
static void
print_operand_nasm(FILE *out, struct gas_operand *op, int postfix) {
char *p;
int i;
if (op->addr_mode != 0 && postfix) {
if (postfix == 'b') {
p = "byte";
} else if (postfix == 'w') {
p = "word";
} else if (postfix == 'l') {
p = "dword";
} else if (postfix == 'q') {
p = "qword";
} else if (postfix == 't') {
p = "tword";
} else {
abort();
}
x_fprintf(out, "%s ", p);
}
switch (op->addr_mode) {
case 0:
print_item_nasm(out, op->items[0], op->item_types[0], postfix);
break;
case ADDR_INDIRECT:
x_fputc('[', out);
print_item_nasm(out, op->items[0], op->item_types[0], postfix);
x_fputc(']', out);
break;
case ADDR_SCALED:
case ADDR_SCALED_DISPLACE:
if (op->addr_mode == ADDR_SCALED) {
i = 0;
} else {
i = 1;
}
x_fputc('[', out);
print_item_nasm(out, op->items[i], op->item_types[i], postfix);
x_fprintf(out, " + ");
++i;
print_item_nasm(out, op->items[i], op->item_types[i], postfix);
++i;
if (op->item_types[i] != 0) {
x_fprintf(out, " * ");
print_item_nasm(out, op->items[i],
op->item_types[i], postfix);
}
if (op->addr_mode == ADDR_SCALED_DISPLACE) {
store_plusminus_num(out, op->items[0]);
}
x_fputc(']', out);
break;
case ADDR_DISPLACE:
x_fputc('[', out);
print_item_nasm(out, op->items[1], op->item_types[1], postfix);
store_plusminus_num(out, op->items[0]);
x_fputc(']', out);
break;
default:
printf("UNKNOWN ADDRESSING MODE %d\n", op->addr_mode);
abort();
}
}
static void
print_operand_gas(FILE *out, struct gas_operand *op, int postfix) {
int i;
switch (op->addr_mode) {
case 0:
print_item_gas_x86(out, op->items[0], op->item_types[0], postfix);
break;
case ADDR_INDIRECT:
x_fputc('(', out);
print_item_gas_x86(out, op->items[0], op->item_types[0], postfix);
x_fputc(')', out);
break;
case ADDR_SCALED:
case ADDR_SCALED_DISPLACE:
if (op->addr_mode == ADDR_SCALED) {
i = 0;
} else {
i = 1;
}
x_fputc('(', out);
print_item_gas_x86(out, op->items[i], op->item_types[i], postfix);
x_fprintf(out, ",");
++i;
print_item_gas_x86(out, op->items[i], op->item_types[i], postfix);
++i;
if (op->item_types[i] != 0) {
x_fprintf(out, ",");
print_item_gas_x86(out, op->items[i],
op->item_types[i], postfix);
}
if (op->addr_mode == ADDR_SCALED_DISPLACE) {
store_plusminus_num(out, op->items[0]);
}
x_fputc(')', out);
break;
case ADDR_DISPLACE:
store_plusminus_num(out, op->items[0]);
x_fputc('(', out);
print_item_gas_x86(out, op->items[1], op->item_types[1], postfix);
x_fputc(')', out);
break;
default:
printf("UNKNOWN ADDRESSING MODE %d\n", op->addr_mode);
abort();
}
}
static void
do_xlat_nasm(
FILE *out,
/*s truct gas_token *instr_tok,*/ char *name,
struct gas_operand *op,
struct gas_operand *op2,
struct gas_operand *op3,
int postfix) {
x_fprintf(out, "%s", name);
if (op == NULL && postfix == 'l') {
/* stosd, lodsd, etc */
x_fputc('d', out);
}
if (op != NULL) x_fputc(' ', out);
if (op3 != NULL) {
print_operand_nasm(out, op3, postfix);
x_fprintf(out, ", ");
}
if (op2 != NULL) {
print_operand_nasm(out, op2, postfix);
x_fprintf(out, ", ");
}
if (op != NULL) {
print_operand_nasm(out, op, postfix);
}
}
static void
do_xlat_gas(
FILE *out,
/*s truct gas_token *instr_tok,*/ char *name,
struct gas_operand *op,
struct gas_operand *op2,
struct gas_operand *op3,
int postfix) {
x_fprintf(out, "%s", name);
if (postfix) {
x_fputc(postfix, out);
}
if (op != NULL) x_fputc(' ', out);
if (op != NULL) {
print_operand_gas(out, op, postfix);
}
if (op2 != NULL) {
x_fprintf(out, ", ");
print_operand_gas(out, op2, postfix);
}
if (op3 != NULL) {
x_fprintf(out, ", ");
print_operand_gas(out, op3, postfix);
}
}
/*
* Returns instruction operand size (b for byte, w=word, l=longword,
* q=qword, t=tword) or zero, in which case no size is specified.
*
* The operand size is sometimes encoded by the last character and
* sometimes not. For example, ``int'' ends with t but doesn't take
* a tword argument. If the return value is nonzero, the encoding
* last character will be removed from the string.
*
* XXX This stuff is quite probably incorrect for a vast number of
* instructions :-(
*/
static int
get_instr_postfix(char *instr, struct gas_operand *op) {
char *p;
int postfix = 0;
p = strchr(instr, 0);
--p;
if (instr[0] == 'f') {
/* Floating point */
if (strchr("slt", *p) != NULL) {
/* short (float), long (double) or tword (ldouble) */
postfix = *p;
*p = 0;
}
} else if (op != NULL) { /* XXX correct? */
if (strchr("bwlq", *p) != NULL) {
/* byte/word/longword(dword)/qword */
if (strcmp(instr, "leal") == 0) {
/*
* For some reason,
* lea eax, dword [eax]
* doesn't work in NASM, so let's
* omit the size
*/
;
} else {
postfix = *p;
}
*p = 0;
}
} else if (op == NULL && *p == 'l') {
/*
* stosd, lodsd, etc are written as stosl, etc
*/
postfix = *p;
*p = 0;
}
return postfix;
}
static void
append_instr(
struct inline_instr **dest,
struct inline_instr **dest_tail,
struct inline_instr *instr) {
if (*dest == NULL) {
*dest = *dest_tail = instr;
} else {
(*dest_tail)->next = instr;
*dest_tail = (*dest_tail)->next;
}
}
static struct inline_instr *
do_parse_asm(struct gas_token *tok, struct inline_asm_stmt *stmt) {
struct gas_token *t;
struct gas_token *nexttok;
struct inline_instr *ret = NULL;
struct inline_instr *ret_tail = NULL;
struct inline_instr *tmp;
static struct inline_instr nullinstr;
for (t = tok; t != NULL;) {
if (t->type == GAS_DOTIDENT) {
/* Assembler directive */
warningfl(asm_tok,
"Ignoring assembler directive `%s'",
t->ascii);
do {
t = t->next;
} while (t && t->type != GAS_SEPARATOR);
} else if (t->type == GAS_SEPARATOR) {
;
} else if ((t->type == GAS_IDENT
|| GAS_NUMBER(t->type))
&& t->next
&& t->next->type == TOK_OP_AMB_COND2) {
/* Label */
tmp = n_xmalloc(sizeof *tmp);
*tmp = nullinstr;
tmp->type = INLINSTR_LABEL;
if (t->type == GAS_IDENT) {
tmp->name = t->data;
} else {
tmp->name
= backend->get_inlineasm_label(t->data);
}
t = t->next;
append_instr(&ret, &ret_tail, tmp);
} else if (t->type == GAS_IDENT) {
/* Has to be instruction!? */
struct gas_operand *op = NULL;
struct gas_operand *op2 = NULL;
struct gas_token *t2;
struct gas_token *instr_tok = t;
t2 = t->next;
if (t2
&& t2->type != GAS_SEPARATOR
&& (op = parse_operand(&t2, stmt)) != NULL) {
t = t2;
if (t2 && t2->type == TOK_OP_COMMA) {
if ((t2 = t2->next) &&
(op2 = parse_operand(&t2, stmt))
!= NULL) {
t = t2;
}
}
}
if (gas_errors) {
break;
}
tmp = n_xmalloc(sizeof *tmp);
*tmp = nullinstr;
tmp->type = INLINSTR_REAL;
tmp->name = instr_tok->data;
tmp->postfix = get_instr_postfix(instr_tok->data, op);
tmp->operands[0] = op;
tmp->operands[1] = op2;
tmp->operands[2] = NULL;
append_instr(&ret, &ret_tail, tmp);
} else {
gas_errorfl(t, "Parse error at `%s' (%d)",
t->ascii, __LINE__);
}
if (t == NULL) {
break;
}
nexttok = t->next;
/*free_gas_token(t);*/
t = nexttok;
}
if (gas_errors) {
free(ret);
return NULL;
}
return ret;
}
static void
inline_instr_to_gas_or_nasm(FILE *out, struct inline_instr *code, int to) {
struct inline_instr *tmp;
for (tmp = code; tmp != NULL; tmp = tmp->next) {
if (tmp->type == INLINSTR_REAL) {
if (to == TO_NASM) {
do_xlat_nasm(out, tmp->name, tmp->operands[0],
tmp->operands[1], NULL, tmp->postfix);
} else {
do_xlat_gas(out, tmp->name, tmp->operands[0],
tmp->operands[1], NULL, tmp->postfix);
}
} else if (tmp->type == INLINSTR_LABEL) {
emit->label(tmp->name, 0);
} else if (tmp->type == INLINSTR_ASM) {
}
x_fputc('\n', out);
}
}
void
inline_instr_to_nasm(FILE *out, struct inline_instr *code) {
inline_instr_to_gas_or_nasm(out, code, TO_NASM);
}
void
inline_instr_to_gas(FILE *out, struct inline_instr *code) {
inline_instr_to_gas_or_nasm(out, code, TO_GAS);
}
static struct inline_asm_io *
get_iolist(struct token **tok, int is_output, int *err, int *n_items,
struct inline_asm_stmt *data) {
struct token *t;
struct inline_asm_io *ret = NULL;
struct inline_asm_io *ret_tail = NULL;
struct expr *ex;
*n_items = 0;
*err = 1;
if (next_token(tok) != 0) {
return NULL;
}
for (t = *tok; t != NULL; t = t->next) {
if (t->type == TOK_OPERATOR
&& *(int *)t->data == TOK_OP_AMB_COND2) {
/* Terminating : */
break;
} else if (t->type == TOK_PAREN_CLOSE) {
/* Entire asm statement ends here */
break;
} else if (t->type == TOK_STRING_LITERAL) {
struct inline_asm_io *tmp;
char *constraints = t->ascii;
if (is_output) {
char *p;
if ((p = strchr(constraints, '=')) == NULL) {
if (strchr(constraints, '+') == NULL) {
errorfl(t, "Output operand "
"constraints lack `='");
return NULL;
}
} else if (constraints[0] != '=') {
warningfl(t, "Output operand "
"constraints don't begin "
"with `='");
memmove(p, p + 1, strlen(p));
}
}
/*
* "constraints" (expr)
*/
if (expect_token(&t, TOK_PAREN_OPEN, 1) != 0) {
return NULL;
}
ex = parse_expr(&t, TOK_PAREN_CLOSE, 0, 0, 1);
if (ex == NULL) {
return NULL;
}
tmp = n_xmalloc(sizeof *tmp);
if (is_output && constraints[0] == '=') {
tmp->constraints = constraints + 1;
} else {
tmp->constraints = constraints;
}
if (isdigit((unsigned char)*tmp->constraints)) {
tmp->constraints = get_constraint_by_idx(
data, *tmp->constraints - '0');
if (tmp->constraints == NULL) {
return NULL;
}
}
tmp->expr = ex; /* XXX list order ok? */
tmp->next = NULL;
tmp->index = *n_items;
if (ret == NULL) {
ret = ret_tail = tmp;
} else {
ret_tail->next = tmp;
ret_tail = tmp;
}
++*n_items;
if (next_token(&t) != 0) {
return NULL;
}
if (t->type == TOK_OPERATOR
&& *(int *)t->data == TOK_OP_COMMA) {
;
} else if (t->type == TOK_PAREN_CLOSE) {
break;
} else if (t->type == TOK_OPERATOR
&& *(int *)t->data == TOK_OP_AMB_COND2) {
break;
} else {
errorfl(t, "Parse error at `%s'", t->ascii);
return NULL;
}
} else {
errorfl(t, "Parse error at `%s'", t->ascii);
return NULL;
}
}
*err = 0;
*tok = t;
return ret;
}
static void
free_iolist(struct inline_asm_io *io) {
struct inline_asm_io *tmp;
while (io != NULL) {
tmp = io->next;
free(io);
io = tmp;
}
}
struct inline_asm_stmt *
parse_inline_asm(struct token **tok) {
struct token *t;
struct inline_asm_stmt *ret;
static struct inline_asm_stmt nullstmt;
struct gas_token *gt = NULL;
static int warned = 0;
int err;
#ifndef __FreeBSD__ /* FreeBSD uses inline asm heavily in the headers ... */
if (!warned) {
warningfl(*tok, "nwcc inline assembly support is new, "
"incomplete and unproven");
warned = 1;
}
#endif
gas_errors = 0;
if ((*tok)->next && (*tok)->next->type == TOK_KEY_VOLATILE) {
/*
* __asm__ __volatile__("...");
*/
if (next_token(tok) != 0) {
return NULL;
}
}
if (expect_token(tok, TOK_PAREN_OPEN, 1) != 0) {
return NULL;
}
asm_tok = t = *tok;
if (t->type != TOK_STRING_LITERAL) {
errorfl(t, "Parse error at `%s' (%d)",
t->ascii, __LINE__);
return NULL;
}
/*
* Syntax:
* "code" : output : input : clobbered
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ optional
*/
ret = n_xmalloc(sizeof *ret);
*ret = nullstmt;
if (*t->ascii != 0) {
if ((gt = tokenize_gas_code(t->ascii)) == NULL) {
recover(&t, TOK_PAREN_CLOSE, 0);
return NULL;
}
}
if (next_token(&t) != 0) {
return NULL;
}
err = 0;
if (t->type == TOK_OPERATOR
&& *(int *)t->data == TOK_OP_AMB_COND2) {
int n;
ret->extended = 1;
/* Output specified? */
if ((ret->output = get_iolist(&t, 1, &err, &n, ret)) == NULL
&& err) {
} else if (t->type == TOK_OPERATOR
&& *(int *)t->data == TOK_OP_AMB_COND2) {
ret->n_outputs = n;
/* Input specified? */
if ((ret->input = get_iolist(&t, 0, &err, &n, ret)) == NULL
&& err) {
free_iolist(ret->output);
} else {
ret->n_inputs = n;
}
} else {
ret->n_outputs = n;
}
if (err == 0
&& t->type == TOK_OPERATOR
&& *(int *)t->data == TOK_OP_AMB_COND2) {
/* Clobbered register list */
for (t = t->next; t != NULL; t = t->next) {
if (t->type == TOK_STRING_LITERAL) {
struct clobbered_reg *cr;
cr = n_xmalloc(sizeof *cr);
cr->reg = backend->
name_to_reg(t->ascii);
if (cr->reg == NULL
&& strcmp(t->ascii, "memory")
!= 0
&& strcmp(t->ascii, "cc") != 0) {
errorfl(t,
"Unknown register `%s'",
t->ascii);
return NULL;
}
cr->next = ret->clobbered;
ret->clobbered = cr;
if ((t = t->next) == NULL) {
break;
} else if (t->type == TOK_OPERATOR
&& *(int *)t->data
== TOK_OP_COMMA) {
; /* ok */
} else if (t->type
== TOK_PAREN_CLOSE) {
break;
}
} else {
errorfl(t, "Parse error at `%s'",
t->ascii);
return NULL;
}
}
if (t == NULL) {
errorfl(NULL, "Premature end of file");
return NULL;
}
}
} else if (t->type == TOK_PAREN_CLOSE) {
; /* done */
}
if (t->type != TOK_PAREN_CLOSE) {
errorfl(t, "Parse error at `%s' (%d)",
t->ascii, __LINE__);
recover(&t, TOK_PAREN_CLOSE, 0);
return NULL;
}
*tok = t;
if (!err && gt != NULL) {
ret->code = do_parse_asm(gt, ret);
}
/*free_gas_token_list(gt);*/
if (err) {
free(ret);
return NULL;
}
ret->toklist = gt;
return ret;
}
char *
parse_asm_varname(struct token **curtok) {
char *asmname;
if (expect_token(curtok, TOK_PAREN_OPEN, 1) != 0) {
return NULL;
}
if ((*curtok)->type != TOK_STRING_LITERAL) {
errorfl(*curtok, "Parse error at `%s'", (*curtok)->ascii);
return NULL;
}
asmname = (*curtok)->ascii;
if (expect_token(curtok, TOK_PAREN_CLOSE, 1) != 0) {
return NULL;
}
return asmname;
}
syntax highlighted by Code2HTML, v. 0.9.1