blob: 2ee7b35ecfa532f548ee00fa63732bc1f126e7a2 [file] [log] [blame]
/*
* sparse/compile-i386.c
*
* Copyright (C) 2003 Transmeta Corp.
* 2003 Linus Torvalds
* Copyright 2003 Jeff Garzik
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* x86 backend
*
* TODO list:
* in general, any non-32bit SYM_BASETYPE is unlikely to work.
* complex initializers
* bitfields
* global struct/union variables
* addressing structures, and members of structures (as opposed to
* scalars) on the stack. Requires smarter stack frame allocation.
* labels / goto
* any function argument that isn't 32 bits (or promoted to such)
* inline asm
* floating point
*
*/
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include "lib.h"
#include "allocate.h"
#include "token.h"
#include "parse.h"
#include "symbol.h"
#include "scope.h"
#include "expression.h"
#include "target.h"
#include "compile.h"
#include "bitmap.h"
#include "version.h"
struct textbuf {
unsigned int len; /* does NOT include terminating null */
char *text;
struct textbuf *next;
struct textbuf *prev;
};
struct loop_stack {
int continue_lbl;
int loop_bottom_lbl;
struct loop_stack *next;
};
struct atom;
struct storage;
DECLARE_PTR_LIST(str_list, struct atom);
DECLARE_PTR_LIST(atom_list, struct atom);
DECLARE_PTR_LIST(storage_list, struct storage);
struct function {
int stack_size;
int pseudo_nr;
struct storage_list *pseudo_list;
struct atom_list *atom_list;
struct str_list *str_list;
struct loop_stack *loop_stack;
struct symbol **argv;
unsigned int argc;
int ret_target;
};
enum storage_type {
STOR_PSEUDO, /* variable stored on the stack */
STOR_ARG, /* function argument */
STOR_SYM, /* a symbol we can directly ref in the asm */
STOR_REG, /* scratch register */
STOR_VALUE, /* integer constant */
STOR_LABEL, /* label / jump target */
STOR_LABELSYM, /* label generated from symbol's pointer value */
};
struct reg_info {
const char *name;
struct storage *contains;
const unsigned char aliases[12];
#define own_regno aliases[0]
};
struct storage {
enum storage_type type;
unsigned long flags;
/* STOR_REG */
struct reg_info *reg;
struct symbol *ctype;
union {
/* STOR_PSEUDO */
struct {
int pseudo;
int offset;
int size;
};
/* STOR_ARG */
struct {
int idx;
};
/* STOR_SYM */
struct {
struct symbol *sym;
};
/* STOR_VALUE */
struct {
long long value;
};
/* STOR_LABEL */
struct {
int label;
};
/* STOR_LABELSYM */
struct {
struct symbol *labelsym;
};
};
};
enum {
STOR_LABEL_VAL = (1 << 0),
STOR_WANTS_FREE = (1 << 1),
};
struct symbol_private {
struct storage *addr;
};
enum atom_type {
ATOM_TEXT,
ATOM_INSN,
ATOM_CSTR,
};
struct atom {
enum atom_type type;
union {
/* stuff for text */
struct {
char *text;
unsigned int text_len; /* w/o terminating null */
};
/* stuff for insns */
struct {
char insn[32];
char comment[40];
struct storage *op1;
struct storage *op2;
};
/* stuff for C strings */
struct {
struct string *string;
int label;
};
};
};
static struct function *current_func = NULL;
static struct textbuf *unit_post_text = NULL;
static const char *current_section;
static void emit_comment(const char * fmt, ...) FORMAT_ATTR(1);
static void emit_move(struct storage *src, struct storage *dest,
struct symbol *ctype, const char *comment);
static struct storage *x86_address_gen(struct expression *expr);
static struct storage *x86_symbol_expr(struct symbol *sym);
static void x86_symbol(struct symbol *sym);
static struct storage *x86_statement(struct statement *stmt);
static struct storage *x86_expression(struct expression *expr);
enum registers {
NOREG,
AL, DL, CL, BL, AH, DH, CH, BH, // 8-bit
AX, DX, CX, BX, SI, DI, BP, SP, // 16-bit
EAX, EDX, ECX, EBX, ESI, EDI, EBP, ESP, // 32-bit
EAX_EDX, ECX_EBX, ESI_EDI, // 64-bit
};
/* This works on regno's, reg_info's and hardreg_storage's */
#define byte_reg(reg) ((reg) - 16)
#define highbyte_reg(reg) ((reg)-12)
#define word_reg(reg) ((reg)-8)
#define REGINFO(nr, str, conflicts...) [nr] = { .name = str, .aliases = { nr , conflicts } }
static struct reg_info reg_info_table[] = {
REGINFO( AL, "%al", AX, EAX, EAX_EDX),
REGINFO( DL, "%dl", DX, EDX, EAX_EDX),
REGINFO( CL, "%cl", CX, ECX, ECX_EBX),
REGINFO( BL, "%bl", BX, EBX, ECX_EBX),
REGINFO( AH, "%ah", AX, EAX, EAX_EDX),
REGINFO( DH, "%dh", DX, EDX, EAX_EDX),
REGINFO( CH, "%ch", CX, ECX, ECX_EBX),
REGINFO( BH, "%bh", BX, EBX, ECX_EBX),
REGINFO( AX, "%ax", AL, AH, EAX, EAX_EDX),
REGINFO( DX, "%dx", DL, DH, EDX, EAX_EDX),
REGINFO( CX, "%cx", CL, CH, ECX, ECX_EBX),
REGINFO( BX, "%bx", BL, BH, EBX, ECX_EBX),
REGINFO( SI, "%si", ESI, ESI_EDI),
REGINFO( DI, "%di", EDI, ESI_EDI),
REGINFO( BP, "%bp", EBP),
REGINFO( SP, "%sp", ESP),
REGINFO(EAX, "%eax", AL, AH, AX, EAX_EDX),
REGINFO(EDX, "%edx", DL, DH, DX, EAX_EDX),
REGINFO(ECX, "%ecx", CL, CH, CX, ECX_EBX),
REGINFO(EBX, "%ebx", BL, BH, BX, ECX_EBX),
REGINFO(ESI, "%esi", SI, ESI_EDI),
REGINFO(EDI, "%edi", DI, ESI_EDI),
REGINFO(EBP, "%ebp", BP),
REGINFO(ESP, "%esp", SP),
REGINFO(EAX_EDX, "%eax:%edx", AL, AH, AX, EAX, DL, DH, DX, EDX),
REGINFO(ECX_EBX, "%ecx:%ebx", CL, CH, CX, ECX, BL, BH, BX, EBX),
REGINFO(ESI_EDI, "%esi:%edi", SI, ESI, DI, EDI),
};
#define REGSTORAGE(nr) [nr] = { .type = STOR_REG, .reg = reg_info_table + (nr) }
static struct storage hardreg_storage_table[] = {
REGSTORAGE(AL), REGSTORAGE(DL), REGSTORAGE(CL), REGSTORAGE(BL),
REGSTORAGE(AH), REGSTORAGE(DH), REGSTORAGE(CH), REGSTORAGE(BH),
REGSTORAGE(AX), REGSTORAGE(DX), REGSTORAGE(CX), REGSTORAGE(BX),
REGSTORAGE(SI), REGSTORAGE(DI), REGSTORAGE(BP), REGSTORAGE(SP),
REGSTORAGE(EAX), REGSTORAGE(EDX), REGSTORAGE(ECX), REGSTORAGE(EBX),
REGSTORAGE(ESI), REGSTORAGE(EDI), REGSTORAGE(EBP), REGSTORAGE(ESP),
REGSTORAGE(EAX_EDX), REGSTORAGE(ECX_EBX), REGSTORAGE(ESI_EDI),
};
#define REG_EAX (&hardreg_storage_table[EAX])
#define REG_ECX (&hardreg_storage_table[ECX])
#define REG_EDX (&hardreg_storage_table[EDX])
#define REG_ESP (&hardreg_storage_table[ESP])
#define REG_DL (&hardreg_storage_table[DL])
#define REG_DX (&hardreg_storage_table[DX])
#define REG_AL (&hardreg_storage_table[AL])
#define REG_AX (&hardreg_storage_table[AX])
static DECLARE_BITMAP(regs_in_use, 256);
static inline struct storage * reginfo_reg(struct reg_info *info)
{
return hardreg_storage_table + info->own_regno;
}
static struct storage * get_hardreg(struct storage *reg, int clear)
{
struct reg_info *info = reg->reg;
const unsigned char *aliases;
int regno;
aliases = info->aliases;
while ((regno = *aliases++) != NOREG) {
if (test_bit(regno, regs_in_use))
goto busy;
if (clear)
reg_info_table[regno].contains = NULL;
}
set_bit(info->own_regno, regs_in_use);
return reg;
busy:
fprintf(stderr, "register %s is busy\n", info->name);
if (regno + reg_info_table != info)
fprintf(stderr, " conflicts with %s\n", reg_info_table[regno].name);
exit(1);
}
static void put_reg(struct storage *reg)
{
struct reg_info *info = reg->reg;
int regno = info->own_regno;
if (test_and_clear_bit(regno, regs_in_use))
return;
fprintf(stderr, "freeing already free'd register %s\n", reg_info_table[regno].name);
}
struct regclass {
const char *name;
const unsigned char regs[30];
};
static struct regclass regclass_8 = { "8-bit", { AL, DL, CL, BL, AH, DH, CH, BH }};
static struct regclass regclass_16 = { "16-bit", { AX, DX, CX, BX, SI, DI, BP }};
static struct regclass regclass_32 = { "32-bit", { EAX, EDX, ECX, EBX, ESI, EDI, EBP }};
static struct regclass regclass_64 = { "64-bit", { EAX_EDX, ECX_EBX, ESI_EDI }};
static struct regclass regclass_32_8 = { "32-bit bytes", { EAX, EDX, ECX, EBX }};
static struct regclass *get_regclass_bits(int bits)
{
switch (bits) {
case 8: return &regclass_8;
case 16: return &regclass_16;
case 64: return &regclass_64;
default: return &regclass_32;
}
}
static struct regclass *get_regclass(struct expression *expr)
{
return get_regclass_bits(expr->ctype->bit_size);
}
static int register_busy(int regno)
{
if (!test_bit(regno, regs_in_use)) {
struct reg_info *info = reg_info_table + regno;
const unsigned char *regs = info->aliases+1;
while ((regno = *regs) != NOREG) {
regs++;
if (test_bit(regno, regs_in_use))
goto busy;
}
return 0;
}
busy:
return 1;
}
static struct storage *get_reg(struct regclass *class)
{
const unsigned char *regs = class->regs;
int regno;
while ((regno = *regs) != NOREG) {
regs++;
if (register_busy(regno))
continue;
return get_hardreg(hardreg_storage_table + regno, 1);
}
fprintf(stderr, "Ran out of %s registers\n", class->name);
exit(1);
}
static struct storage *get_reg_value(struct storage *value, struct regclass *class)
{
struct reg_info *info;
struct storage *reg;
/* Do we already have it somewhere */
info = value->reg;
if (info && info->contains == value) {
emit_comment("already have register %s", info->name);
return get_hardreg(hardreg_storage_table + info->own_regno, 0);
}
reg = get_reg(class);
emit_move(value, reg, value->ctype, "reload register");
info = reg->reg;
info->contains = value;
value->reg = info;
return reg;
}
static struct storage *temp_from_bits(unsigned int bit_size)
{
return get_reg(get_regclass_bits(bit_size));
}
static inline unsigned int pseudo_offset(struct storage *s)
{
if (s->type != STOR_PSEUDO)
return 123456; /* intentionally bogus value */
return s->offset;
}
static inline unsigned int arg_offset(struct storage *s)
{
if (s->type != STOR_ARG)
return 123456; /* intentionally bogus value */
/* FIXME: this is wrong wrong wrong */
return current_func->stack_size + ((1 + s->idx) * 4);
}
static const char *pretty_offset(int ofs)
{
static char esp_buf[64];
if (ofs)
sprintf(esp_buf, "%d(%%esp)", ofs);
else
strcpy(esp_buf, "(%esp)");
return esp_buf;
}
static void stor_sym_init(struct symbol *sym)
{
struct storage *stor;
struct symbol_private *priv;
priv = calloc(1, sizeof(*priv) + sizeof(*stor));
if (!priv)
die("OOM in stor_sym_init");
stor = (struct storage *) (priv + 1);
priv->addr = stor;
stor->type = STOR_SYM;
stor->sym = sym;
}
static const char *stor_op_name(struct storage *s)
{
static char name[32];
switch (s->type) {
case STOR_PSEUDO:
strcpy(name, pretty_offset((int) pseudo_offset(s)));
break;
case STOR_ARG:
strcpy(name, pretty_offset((int) arg_offset(s)));
break;
case STOR_SYM:
strcpy(name, show_ident(s->sym->ident));
break;
case STOR_REG:
strcpy(name, s->reg->name);
break;
case STOR_VALUE:
sprintf(name, "$%lld", s->value);
break;
case STOR_LABEL:
sprintf(name, "%s.L%d", s->flags & STOR_LABEL_VAL ? "$" : "",
s->label);
break;
case STOR_LABELSYM:
sprintf(name, "%s.LS%p", s->flags & STOR_LABEL_VAL ? "$" : "",
s->labelsym);
break;
}
return name;
}
static struct atom *new_atom(enum atom_type type)
{
struct atom *atom;
atom = calloc(1, sizeof(*atom)); /* TODO: chunked alloc */
if (!atom)
die("nuclear OOM");
atom->type = type;
return atom;
}
static inline void push_cstring(struct function *f, struct string *str,
int label)
{
struct atom *atom;
atom = new_atom(ATOM_CSTR);
atom->string = str;
atom->label = label;
add_ptr_list(&f->str_list, atom); /* note: _not_ atom_list */
}
static inline void push_atom(struct function *f, struct atom *atom)
{
add_ptr_list(&f->atom_list, atom);
}
static void push_text_atom(struct function *f, const char *text)
{
struct atom *atom = new_atom(ATOM_TEXT);
atom->text = strdup(text);
atom->text_len = strlen(text);
push_atom(f, atom);
}
static struct storage *new_storage(enum storage_type type)
{
struct storage *stor;
stor = calloc(1, sizeof(*stor));
if (!stor)
die("OOM in new_storage");
stor->type = type;
return stor;
}
static struct storage *stack_alloc(int n_bytes)
{
struct function *f = current_func;
struct storage *stor;
assert(f != NULL);
stor = new_storage(STOR_PSEUDO);
stor->type = STOR_PSEUDO;
stor->pseudo = f->pseudo_nr;
stor->offset = f->stack_size; /* FIXME: stack req. natural align */
stor->size = n_bytes;
f->stack_size += n_bytes;
f->pseudo_nr++;
add_ptr_list(&f->pseudo_list, stor);
return stor;
}
static struct storage *new_labelsym(struct symbol *sym)
{
struct storage *stor;
stor = new_storage(STOR_LABELSYM);
if (stor) {
stor->flags |= STOR_WANTS_FREE;
stor->labelsym = sym;
}
return stor;
}
static struct storage *new_val(long long value)
{
struct storage *stor;
stor = new_storage(STOR_VALUE);
if (stor) {
stor->flags |= STOR_WANTS_FREE;
stor->value = value;
}
return stor;
}
static int new_label(void)
{
static int label = 0;
return ++label;
}
static void textbuf_push(struct textbuf **buf_p, const char *text)
{
struct textbuf *tmp, *list = *buf_p;
unsigned int text_len = strlen(text);
unsigned int alloc_len = text_len + 1 + sizeof(*list);
tmp = calloc(1, alloc_len);
if (!tmp)
die("OOM on textbuf alloc");
tmp->text = ((void *) tmp) + sizeof(*tmp);
memcpy(tmp->text, text, text_len + 1);
tmp->len = text_len;
/* add to end of list */
if (!list) {
list = tmp;
tmp->prev = tmp;
} else {
tmp->prev = list->prev;
tmp->prev->next = tmp;
list->prev = tmp;
}
tmp->next = list;
*buf_p = list;
}
static void textbuf_emit(struct textbuf **buf_p)
{
struct textbuf *tmp, *list = *buf_p;
while (list) {
tmp = list;
if (tmp->next == tmp)
list = NULL;
else {
tmp->prev->next = tmp->next;
tmp->next->prev = tmp->prev;
list = tmp->next;
}
fputs(tmp->text, stdout);
free(tmp);
}
*buf_p = list;
}
static void insn(const char *insn, struct storage *op1, struct storage *op2,
const char *comment_in)
{
struct function *f = current_func;
struct atom *atom = new_atom(ATOM_INSN);
assert(insn != NULL);
strcpy(atom->insn, insn);
if (comment_in && (*comment_in))
strncpy(atom->comment, comment_in,
sizeof(atom->comment) - 1);
atom->op1 = op1;
atom->op2 = op2;
push_atom(f, atom);
}
static void emit_comment(const char *fmt, ...)
{
struct function *f = current_func;
static char tmpbuf[100] = "\t# ";
va_list args;
int i;
va_start(args, fmt);
i = vsnprintf(tmpbuf+3, sizeof(tmpbuf)-4, fmt, args);
va_end(args);
tmpbuf[i+3] = '\n';
tmpbuf[i+4] = '\0';
push_text_atom(f, tmpbuf);
}
static void emit_label (int label, const char *comment)
{
struct function *f = current_func;
char s[64];
if (!comment)
sprintf(s, ".L%d:\n", label);
else
sprintf(s, ".L%d:\t\t\t\t\t# %s\n", label, comment);
push_text_atom(f, s);
}
static void emit_labelsym (struct symbol *sym, const char *comment)
{
struct function *f = current_func;
char s[64];
if (!comment)
sprintf(s, ".LS%p:\n", sym);
else
sprintf(s, ".LS%p:\t\t\t\t# %s\n", sym, comment);
push_text_atom(f, s);
}
void emit_unit_begin(const char *basename)
{
printf("\t.file\t\"%s\"\n", basename);
}
void emit_unit_end(void)
{
textbuf_emit(&unit_post_text);
printf("\t.ident\t\"sparse silly x86 backend (version %s)\"\n", SPARSE_VERSION);
}
/* conditionally switch sections */
static void emit_section(const char *s)
{
if (s == current_section)
return;
if (current_section && (!strcmp(s, current_section)))
return;
printf("\t%s\n", s);
current_section = s;
}
static void emit_insn_atom(struct function *f, struct atom *atom)
{
char s[128];
char comment[64];
struct storage *op1 = atom->op1;
struct storage *op2 = atom->op2;
if (atom->comment[0])
sprintf(comment, "\t\t# %s", atom->comment);
else
comment[0] = 0;
if (atom->op2) {
char tmp[16];
strcpy(tmp, stor_op_name(op1));
sprintf(s, "\t%s\t%s, %s%s\n",
atom->insn, tmp, stor_op_name(op2), comment);
} else if (atom->op1)
sprintf(s, "\t%s\t%s%s%s\n",
atom->insn, stor_op_name(op1),
comment[0] ? "\t" : "", comment);
else
sprintf(s, "\t%s\t%s%s\n",
atom->insn,
comment[0] ? "\t\t" : "", comment);
if (write(STDOUT_FILENO, s, strlen(s)) < 0)
die("can't write to stdout");
}
static void emit_atom_list(struct function *f)
{
struct atom *atom;
FOR_EACH_PTR(f->atom_list, atom) {
switch (atom->type) {
case ATOM_TEXT: {
if (write(STDOUT_FILENO, atom->text, atom->text_len) < 0)
die("can't write to stdout");
break;
}
case ATOM_INSN:
emit_insn_atom(f, atom);
break;
case ATOM_CSTR:
assert(0);
break;
}
} END_FOR_EACH_PTR(atom);
}
static void emit_string_list(struct function *f)
{
struct atom *atom;
emit_section(".section\t.rodata");
FOR_EACH_PTR(f->str_list, atom) {
/* FIXME: escape " in string */
printf(".L%d:\n", atom->label);
printf("\t.string\t%s\n", show_string(atom->string));
free(atom);
} END_FOR_EACH_PTR(atom);
}
static void func_cleanup(struct function *f)
{
struct storage *stor;
struct atom *atom;
FOR_EACH_PTR(f->atom_list, atom) {
if ((atom->type == ATOM_TEXT) && (atom->text))
free(atom->text);
if (atom->op1 && (atom->op1->flags & STOR_WANTS_FREE))
free(atom->op1);
if (atom->op2 && (atom->op2->flags & STOR_WANTS_FREE))
free(atom->op2);
free(atom);
} END_FOR_EACH_PTR(atom);
FOR_EACH_PTR(f->pseudo_list, stor) {
free(stor);
} END_FOR_EACH_PTR(stor);
free_ptr_list(&f->pseudo_list);
free(f);
}
/* function prologue */
static void emit_func_pre(struct symbol *sym)
{
struct function *f;
struct symbol *arg;
unsigned int i, argc = 0, alloc_len;
unsigned char *mem;
struct symbol_private *privbase;
struct storage *storage_base;
struct symbol *base_type = sym->ctype.base_type;
FOR_EACH_PTR(base_type->arguments, arg) {
argc++;
} END_FOR_EACH_PTR(arg);
alloc_len =
sizeof(*f) +
(argc * sizeof(struct symbol *)) +
(argc * sizeof(struct symbol_private)) +
(argc * sizeof(struct storage));
mem = calloc(1, alloc_len);
if (!mem)
die("OOM on func info");
f = (struct function *) mem;
mem += sizeof(*f);
f->argv = (struct symbol **) mem;
mem += (argc * sizeof(struct symbol *));
privbase = (struct symbol_private *) mem;
mem += (argc * sizeof(struct symbol_private));
storage_base = (struct storage *) mem;
f->argc = argc;
f->ret_target = new_label();
i = 0;
FOR_EACH_PTR(base_type->arguments, arg) {
f->argv[i] = arg;
arg->aux = &privbase[i];
storage_base[i].type = STOR_ARG;
storage_base[i].idx = i;
privbase[i].addr = &storage_base[i];
i++;
} END_FOR_EACH_PTR(arg);
assert(current_func == NULL);
current_func = f;
}
/* function epilogue */
static void emit_func_post(struct symbol *sym)
{
const char *name = show_ident(sym->ident);
struct function *f = current_func;
int stack_size = f->stack_size;
if (f->str_list)
emit_string_list(f);
/* function prologue */
emit_section(".text");
if ((sym->ctype.modifiers & MOD_STATIC) == 0)
printf(".globl %s\n", name);
printf("\t.type\t%s, @function\n", name);
printf("%s:\n", name);
if (stack_size) {
char pseudo_const[16];
sprintf(pseudo_const, "$%d", stack_size);
printf("\tsubl\t%s, %%esp\n", pseudo_const);
}
/* function epilogue */
/* jump target for 'return' statements */
emit_label(f->ret_target, NULL);
if (stack_size) {
struct storage *val;
val = new_storage(STOR_VALUE);
val->value = (long long) (stack_size);
val->flags = STOR_WANTS_FREE;
insn("addl", val, REG_ESP, NULL);
}
insn("ret", NULL, NULL, NULL);
/* output everything to stdout */
fflush(stdout); /* paranoia; needed? */
emit_atom_list(f);
/* function footer */
name = show_ident(sym->ident);
printf("\t.size\t%s, .-%s\n", name, name);
func_cleanup(f);
current_func = NULL;
}
/* emit object (a.k.a. variable, a.k.a. data) prologue */
static void emit_object_pre(const char *name, unsigned long modifiers,
unsigned long alignment, unsigned int byte_size)
{
if ((modifiers & MOD_STATIC) == 0)
printf(".globl %s\n", name);
emit_section(".data");
if (alignment)
printf("\t.align %lu\n", alignment);
printf("\t.type\t%s, @object\n", name);
printf("\t.size\t%s, %d\n", name, byte_size);
printf("%s:\n", name);
}
/* emit value (only) for an initializer scalar */
static void emit_scalar(struct expression *expr, unsigned int bit_size)
{
const char *type;
long long ll;
assert(expr->type == EXPR_VALUE);
if (expr->value == 0ULL) {
printf("\t.zero\t%d\n", bit_size / 8);
return;
}
ll = (long long) expr->value;
switch (bit_size) {
case 8: type = "byte"; ll = (char) ll; break;
case 16: type = "value"; ll = (short) ll; break;
case 32: type = "long"; ll = (int) ll; break;
case 64: type = "quad"; break;
default: type = NULL; break;
}
assert(type != NULL);
printf("\t.%s\t%lld\n", type, ll);
}
static void emit_global_noinit(const char *name, unsigned long modifiers,
unsigned long alignment, unsigned int byte_size)
{
char s[64];
if (modifiers & MOD_STATIC) {
sprintf(s, "\t.local\t%s\n", name);
textbuf_push(&unit_post_text, s);
}
if (alignment)
sprintf(s, "\t.comm\t%s,%d,%lu\n", name, byte_size, alignment);
else
sprintf(s, "\t.comm\t%s,%d\n", name, byte_size);
textbuf_push(&unit_post_text, s);
}
static int ea_current, ea_last;
static void emit_initializer(struct symbol *sym,
struct expression *expr)
{
int distance = ea_current - ea_last - 1;
if (distance > 0)
printf("\t.zero\t%d\n", (sym->bit_size / 8) * distance);
if (expr->type == EXPR_VALUE) {
struct symbol *base_type = sym->ctype.base_type;
assert(base_type != NULL);
emit_scalar(expr, sym->bit_size / get_expression_value(base_type->array_size));
return;
}
if (expr->type != EXPR_INITIALIZER)
return;
assert(0); /* FIXME */
}
static int sort_array_cmp(const struct expression *a,
const struct expression *b)
{
int a_ofs = 0, b_ofs = 0;
if (a->type == EXPR_POS)
a_ofs = (int) a->init_offset;
if (b->type == EXPR_POS)
b_ofs = (int) b->init_offset;
return a_ofs - b_ofs;
}
/* move to front-end? */
static void sort_array(struct expression *expr)
{
struct expression *entry, **list;
unsigned int elem, sorted, i;
elem = expression_list_size(expr->expr_list);
if (!elem)
return;
list = malloc(sizeof(entry) * elem);
if (!list)
die("OOM in sort_array");
/* this code is no doubt evil and ignores EXPR_INDEX possibly
* to its detriment and other nasty things. improvements
* welcome.
*/
i = 0;
sorted = 0;
FOR_EACH_PTR(expr->expr_list, entry) {
if ((entry->type == EXPR_POS) || (entry->type == EXPR_VALUE)) {
/* add entry to list[], in sorted order */
if (sorted == 0) {
list[0] = entry;
sorted = 1;
} else {
for (i = 0; i < sorted; i++)
if (sort_array_cmp(entry, list[i]) <= 0)
break;
/* If inserting into the middle of list[]
* instead of appending, we memmove.
* This is ugly, but thankfully
* uncommon. Input data with tons of
* entries very rarely have explicit
* offsets. convert to qsort eventually...
*/
if (i != sorted)
memmove(&list[i + 1], &list[i],
(sorted - i) * sizeof(entry));
list[i] = entry;
sorted++;
}
}
} END_FOR_EACH_PTR(entry);
i = 0;
FOR_EACH_PTR(expr->expr_list, entry) {
if ((entry->type == EXPR_POS) || (entry->type == EXPR_VALUE))
*THIS_ADDRESS(entry) = list[i++];
} END_FOR_EACH_PTR(entry);
free(list);
}
static void emit_array(struct symbol *sym)
{
struct symbol *base_type = sym->ctype.base_type;
struct expression *expr = sym->initializer;
struct expression *entry;
assert(base_type != NULL);
stor_sym_init(sym);
ea_last = -1;
emit_object_pre(show_ident(sym->ident), sym->ctype.modifiers,
sym->ctype.alignment,
sym->bit_size / 8);
sort_array(expr);
FOR_EACH_PTR(expr->expr_list, entry) {
if (entry->type == EXPR_VALUE) {
ea_current = 0;
emit_initializer(sym, entry);
ea_last = ea_current;
} else if (entry->type == EXPR_POS) {
ea_current =
entry->init_offset / (base_type->bit_size / 8);
emit_initializer(sym, entry->init_expr);
ea_last = ea_current;
}
} END_FOR_EACH_PTR(entry);
}
void emit_one_symbol(struct symbol *sym)
{
x86_symbol(sym);
}
static void emit_copy(struct storage *dest, struct storage *src,
struct symbol *ctype)
{
struct storage *reg = NULL;
unsigned int bit_size;
/* FIXME: Bitfield copy! */
bit_size = src->size * 8;
if (!bit_size)
bit_size = 32;
if ((src->type == STOR_ARG) && (bit_size < 32))
bit_size = 32;
reg = temp_from_bits(bit_size);
emit_move(src, reg, ctype, "begin copy ..");
bit_size = dest->size * 8;
if (!bit_size)
bit_size = 32;
if ((dest->type == STOR_ARG) && (bit_size < 32))
bit_size = 32;
emit_move(reg, dest, ctype, ".... end copy");
put_reg(reg);
}
static void emit_store(struct expression *dest_expr, struct storage *dest,
struct storage *src, int bits)
{
/* FIXME: Bitfield store! */
printf("\tst.%d\t\tv%d,[v%d]\n", bits, src->pseudo, dest->pseudo);
}
static void emit_scalar_noinit(struct symbol *sym)
{
emit_global_noinit(show_ident(sym->ident),
sym->ctype.modifiers, sym->ctype.alignment,
sym->bit_size / 8);
stor_sym_init(sym);
}
static void emit_array_noinit(struct symbol *sym)
{
emit_global_noinit(show_ident(sym->ident),
sym->ctype.modifiers, sym->ctype.alignment,
get_expression_value(sym->array_size) * (sym->bit_size / 8));
stor_sym_init(sym);
}
static const char *opbits(const char *insn, unsigned int bits)
{
static char opbits_str[32];
char c;
switch (bits) {
case 8: c = 'b'; break;
case 16: c = 'w'; break;
case 32: c = 'l'; break;
case 64: c = 'q'; break;
default: abort(); break;
}
sprintf(opbits_str, "%s%c", insn, c);
return opbits_str;
}
static void emit_move(struct storage *src, struct storage *dest,
struct symbol *ctype, const char *comment)
{
unsigned int bits;
unsigned int is_signed;
unsigned int is_dest = (src->type == STOR_REG);
const char *opname;
if (ctype) {
bits = ctype->bit_size;
is_signed = is_signed_type(ctype);
} else {
bits = 32;
is_signed = 0;
}
/*
* Are we moving from a register to a register?
* Make the new reg to be the "cache".
*/
if ((dest->type == STOR_REG) && (src->type == STOR_REG)) {
struct storage *backing;
reg_reg_move:
if (dest == src)
return;
backing = src->reg->contains;
if (backing) {
/* Is it still valid? */
if (backing->reg != src->reg)
backing = NULL;
else
backing->reg = dest->reg;
}
dest->reg->contains = backing;
insn("mov", src, dest, NULL);
return;
}
/*
* Are we moving to a register from a non-reg?
*
* See if we have the non-reg source already cached
* in a register..
*/
if (dest->type == STOR_REG) {
if (src->reg) {
struct reg_info *info = src->reg;
if (info->contains == src) {
src = reginfo_reg(info);
goto reg_reg_move;
}
}
dest->reg->contains = src;
src->reg = dest->reg;
}
if (src->type == STOR_REG) {
/* We could just mark the register dirty here and do lazy store.. */
src->reg->contains = dest;
dest->reg = src->reg;
}
if ((bits == 8) || (bits == 16)) {
if (is_dest)
opname = "mov";
else
opname = is_signed ? "movsx" : "movzx";
} else
opname = "mov";
insn(opbits(opname, bits), src, dest, comment);
}
static struct storage *emit_compare(struct expression *expr)
{
struct storage *left = x86_expression(expr->left);
struct storage *right = x86_expression(expr->right);
struct storage *reg1, *reg2;
struct storage *new, *val;
const char *opname = NULL;
unsigned int right_bits = expr->right->ctype->bit_size;
switch(expr->op) {
case '<': opname = "setl"; break;
case '>': opname = "setg"; break;
case SPECIAL_LTE:
opname = "setle"; break;
case SPECIAL_GTE:
opname = "setge"; break;
case SPECIAL_EQUAL: opname = "sete"; break;
case SPECIAL_NOTEQUAL: opname = "setne"; break;
case SPECIAL_UNSIGNED_LT:
opname = "setb"; break;
case SPECIAL_UNSIGNED_GT:
opname = "seta"; break;
case SPECIAL_UNSIGNED_LTE:
opname = "setb"; break;
case SPECIAL_UNSIGNED_GTE:
opname = "setae"; break;
default:
assert(0);
break;
}
/* init EDX to 0 */
val = new_storage(STOR_VALUE);
val->flags = STOR_WANTS_FREE;
reg1 = get_reg(&regclass_32_8);
emit_move(val, reg1, NULL, NULL);
/* move op1 into EAX */
reg2 = get_reg_value(left, get_regclass(expr->left));
/* perform comparison, RHS (op1, right) and LHS (op2, EAX) */
insn(opbits("cmp", right_bits), right, reg2, NULL);
put_reg(reg2);
/* store result of operation, 0 or 1, in DL using SETcc */
insn(opname, byte_reg(reg1), NULL, NULL);
/* finally, store the result (DL) in a new pseudo / stack slot */
new = stack_alloc(4);
emit_move(reg1, new, NULL, "end EXPR_COMPARE");
put_reg(reg1);
return new;
}
static struct storage *emit_value(struct expression *expr)
{
#if 0 /* old and slow way */
struct storage *new = stack_alloc(4);
struct storage *val;
val = new_storage(STOR_VALUE);
val->value = (long long) expr->value;
val->flags = STOR_WANTS_FREE;
insn("movl", val, new, NULL);
return new;
#else
struct storage *val;
val = new_storage(STOR_VALUE);
val->value = (long long) expr->value;
return val; /* FIXME: memory leak */
#endif
}
static struct storage *emit_divide(struct expression *expr, struct storage *left, struct storage *right)
{
struct storage *eax_edx;
struct storage *reg, *new;
struct storage *val = new_storage(STOR_VALUE);
emit_comment("begin DIVIDE");
eax_edx = get_hardreg(hardreg_storage_table + EAX_EDX, 1);
/* init EDX to 0 */
val->flags = STOR_WANTS_FREE;
emit_move(val, REG_EDX, NULL, NULL);
new = stack_alloc(expr->ctype->bit_size / 8);
/* EAX is dividend */
emit_move(left, REG_EAX, NULL, NULL);
reg = get_reg_value(right, &regclass_32);
/* perform binop */
insn("div", reg, REG_EAX, NULL);
put_reg(reg);
reg = REG_EAX;
if (expr->op == '%')
reg = REG_EDX;
emit_move(reg, new, NULL, NULL);
put_reg(eax_edx);
emit_comment("end DIVIDE");
return new;
}
static struct storage *emit_binop(struct expression *expr)
{
struct storage *left = x86_expression(expr->left);
struct storage *right = x86_expression(expr->right);
struct storage *new;
struct storage *dest, *src;
const char *opname = NULL;
const char *suffix = NULL;
char opstr[16];
int is_signed;
/* Divides have special register constraints */
if ((expr->op == '/') || (expr->op == '%'))
return emit_divide(expr, left, right);
is_signed = is_signed_type(expr->ctype);
switch (expr->op) {
case '+':
opname = "add";
break;
case '-':
opname = "sub";
break;
case '&':
opname = "and";
break;
case '|':
opname = "or";
break;
case '^':
opname = "xor";
break;
case SPECIAL_LEFTSHIFT:
opname = "shl";
break;
case SPECIAL_RIGHTSHIFT:
if (is_signed)
opname = "sar";
else
opname = "shr";
break;
case '*':
if (is_signed)
opname = "imul";
else
opname = "mul";
break;
case SPECIAL_LOGICAL_AND:
warning(expr->pos, "bogus bitwise and for logical op (should use '2*setne + and' or something)");
opname = "and";
break;
case SPECIAL_LOGICAL_OR:
warning(expr->pos, "bogus bitwise or for logical op (should use 'or + setne' or something)");
opname = "or";
break;
default:
error_die(expr->pos, "unhandled binop '%s'\n", show_special(expr->op));
break;
}
dest = get_reg_value(right, &regclass_32);
src = get_reg_value(left, &regclass_32);
switch (expr->ctype->bit_size) {
case 8:
suffix = "b";
break;
case 16:
suffix = "w";
break;
case 32:
suffix = "l";
break;
case 64:
suffix = "q"; /* FIXME */
break;
default:
assert(0);
break;
}
snprintf(opstr, sizeof(opstr), "%s%s", opname, suffix);
/* perform binop */
insn(opstr, src, dest, NULL);
put_reg(src);
/* store result in new pseudo / stack slot */
new = stack_alloc(expr->ctype->bit_size / 8);
emit_move(dest, new, NULL, "end EXPR_BINOP");
put_reg(dest);
return new;
}
static int emit_conditional_test(struct storage *val)
{
struct storage *reg;
struct storage *target_val;
int target_false;
/* load result into EAX */
emit_comment("begin if/conditional");
reg = get_reg_value(val, &regclass_32);
/* compare result with zero */
insn("test", reg, reg, NULL);
put_reg(reg);
/* create conditional-failed label to jump to */
target_false = new_label();
target_val = new_storage(STOR_LABEL);
target_val->label = target_false;
target_val->flags = STOR_WANTS_FREE;
insn("jz", target_val, NULL, NULL);
return target_false;
}
static int emit_conditional_end(int target_false)
{
struct storage *cond_end_st;
int cond_end;
/* finished generating code for if-true statement.
* add a jump-to-end jump to avoid falling through
* to the if-false statement code.
*/
cond_end = new_label();
cond_end_st = new_storage(STOR_LABEL);
cond_end_st->label = cond_end;
cond_end_st->flags = STOR_WANTS_FREE;
insn("jmp", cond_end_st, NULL, NULL);
/* if we have both if-true and if-false statements,
* the failed-conditional case will fall through to here
*/
emit_label(target_false, NULL);
return cond_end;
}
static void emit_if_conditional(struct statement *stmt)
{
struct storage *val;
int cond_end;
/* emit test portion of conditional */
val = x86_expression(stmt->if_conditional);
cond_end = emit_conditional_test(val);
/* emit if-true statement */
x86_statement(stmt->if_true);
/* emit if-false statement, if present */
if (stmt->if_false) {
cond_end = emit_conditional_end(cond_end);
x86_statement(stmt->if_false);
}
/* end of conditional; jump target for if-true branch */
emit_label(cond_end, "end if");
}
static struct storage *emit_inc_dec(struct expression *expr, int postop)
{
struct storage *addr = x86_address_gen(expr->unop);
struct storage *retval;
char opname[16];
strcpy(opname, opbits(expr->op == SPECIAL_INCREMENT ? "inc" : "dec",
expr->ctype->bit_size));
if (postop) {
struct storage *new = stack_alloc(4);
emit_copy(new, addr, expr->unop->ctype);
retval = new;
} else
retval = addr;
insn(opname, addr, NULL, NULL);
return retval;
}
static struct storage *emit_postop(struct expression *expr)
{
return emit_inc_dec(expr, 1);
}
static struct storage *emit_return_stmt(struct statement *stmt)
{
struct function *f = current_func;
struct expression *expr = stmt->ret_value;
struct storage *val = NULL, *jmplbl;
if (expr && expr->ctype) {
val = x86_expression(expr);
assert(val != NULL);
emit_move(val, REG_EAX, expr->ctype, "return");
}
jmplbl = new_storage(STOR_LABEL);
jmplbl->flags |= STOR_WANTS_FREE;
jmplbl->label = f->ret_target;
insn("jmp", jmplbl, NULL, NULL);
return val;
}
static struct storage *emit_conditional_expr(struct expression *expr)
{
struct storage *cond, *stot = NULL, *stof = NULL;
struct storage *new = stack_alloc(expr->ctype->bit_size / 8);
int target_false, cond_end;
/* evaluate conditional */
cond = x86_expression(expr->conditional);
target_false = emit_conditional_test(cond);
/* handle if-true part of the expression */
stot = x86_expression(expr->cond_true);
emit_copy(new, stot, expr->ctype);
cond_end = emit_conditional_end(target_false);
/* handle if-false part of the expression */
stof = x86_expression(expr->cond_false);
emit_copy(new, stof, expr->ctype);
/* end of conditional; jump target for if-true branch */
emit_label(cond_end, "end conditional");
return new;
}
static struct storage *emit_select_expr(struct expression *expr)
{
struct storage *cond = x86_expression(expr->conditional);
struct storage *stot = x86_expression(expr->cond_true);
struct storage *stof = x86_expression(expr->cond_false);
struct storage *reg_cond, *reg_true, *reg_false;
struct storage *new = stack_alloc(4);
emit_comment("begin SELECT");
reg_cond = get_reg_value(cond, get_regclass(expr->conditional));
reg_true = get_reg_value(stot, get_regclass(expr));
reg_false = get_reg_value(stof, get_regclass(expr));
/*
* Do the actual select: check the conditional for zero,
* move false over true if zero
*/
insn("test", reg_cond, reg_cond, NULL);
insn("cmovz", reg_false, reg_true, NULL);
/* Store it back */
emit_move(reg_true, new, expr->ctype, NULL);
put_reg(reg_cond);
put_reg(reg_true);
put_reg(reg_false);
emit_comment("end SELECT");
return new;
}
static struct storage *emit_symbol_expr_init(struct symbol *sym)
{
struct expression *expr = sym->initializer;
struct symbol_private *priv = sym->aux;
if (priv == NULL) {
priv = calloc(1, sizeof(*priv));
sym->aux = priv;
if (expr == NULL) {
struct storage *new = stack_alloc(4);
fprintf(stderr, "FIXME! no value for symbol %s. creating pseudo %d (stack offset %d)\n",
show_ident(sym->ident),
new->pseudo, new->pseudo * 4);
priv->addr = new;
} else {
priv->addr = x86_expression(expr);
}
}
return priv->addr;
}
static struct storage *emit_string_expr(struct expression *expr)
{
struct function *f = current_func;
int label = new_label();
struct storage *new;
push_cstring(f, expr->string, label);
new = new_storage(STOR_LABEL);
new->label = label;
new->flags = STOR_LABEL_VAL | STOR_WANTS_FREE;
return new;
}
static struct storage *emit_cast_expr(struct expression *expr)
{
struct symbol *old_type, *new_type;
struct storage *op = x86_expression(expr->cast_expression);
int oldbits, newbits;
struct storage *new;
old_type = expr->cast_expression->ctype;
new_type = expr->cast_type;
oldbits = old_type->bit_size;
newbits = new_type->bit_size;
if (oldbits >= newbits)
return op;
emit_move(op, REG_EAX, old_type, "begin cast ..");
new = stack_alloc(newbits / 8);
emit_move(REG_EAX, new, new_type, ".... end cast");
return new;
}
static struct storage *emit_regular_preop(struct expression *expr)
{
struct storage *target = x86_expression(expr->unop);
struct storage *val, *new = stack_alloc(4);
const char *opname = NULL;
switch (expr->op) {
case '!':
val = new_storage(STOR_VALUE);
val->flags = STOR_WANTS_FREE;
emit_move(val, REG_EDX, NULL, NULL);
emit_move(target, REG_EAX, expr->unop->ctype, NULL);
insn("test", REG_EAX, REG_EAX, NULL);
insn("setz", REG_DL, NULL, NULL);
emit_move(REG_EDX, new, expr->unop->ctype, NULL);
break;
case '~':
opname = "not";
case '-':
if (!opname)
opname = "neg";
emit_move(target, REG_EAX, expr->unop->ctype, NULL);
insn(opname, REG_EAX, NULL, NULL);
emit_move(REG_EAX, new, expr->unop->ctype, NULL);
break;
default:
assert(0);
break;
}
return new;
}
static void emit_case_statement(struct statement *stmt)
{
emit_labelsym(stmt->case_label, NULL);
x86_statement(stmt->case_statement);
}
static void emit_switch_statement(struct statement *stmt)
{
struct storage *val = x86_expression(stmt->switch_expression);
struct symbol *sym, *default_sym = NULL;
struct storage *labelsym, *label;
int switch_end = 0;
emit_move(val, REG_EAX, stmt->switch_expression->ctype, "begin case");
/*
* This is where a _real_ back-end would go through the
* cases to decide whether to use a lookup table or a
* series of comparisons etc
*/
FOR_EACH_PTR(stmt->switch_case->symbol_list, sym) {
struct statement *case_stmt = sym->stmt;
struct expression *expr = case_stmt->case_expression;
struct expression *to = case_stmt->case_to;
/* default: */
if (!expr)
default_sym = sym;
/* case NNN: */
else {
struct storage *case_val = new_val(expr->value);
assert (expr->type == EXPR_VALUE);
insn("cmpl", case_val, REG_EAX, NULL);
if (!to) {
labelsym = new_labelsym(sym);
insn("je", labelsym, NULL, NULL);
} else {
int next_test;
label = new_storage(STOR_LABEL);
label->flags |= STOR_WANTS_FREE;
label->label = next_test = new_label();
/* FIXME: signed/unsigned */
insn("jl", label, NULL, NULL);
case_val = new_val(to->value);
insn("cmpl", case_val, REG_EAX, NULL);
/* TODO: implement and use refcounting... */
label = new_storage(STOR_LABEL);
label->flags |= STOR_WANTS_FREE;
label->label = next_test;
/* FIXME: signed/unsigned */
insn("jg", label, NULL, NULL);
labelsym = new_labelsym(sym);
insn("jmp", labelsym, NULL, NULL);
emit_label(next_test, NULL);
}
}
} END_FOR_EACH_PTR(sym);
if (default_sym) {
labelsym = new_labelsym(default_sym);
insn("jmp", labelsym, NULL, "default");
} else {
label = new_storage(STOR_LABEL);
label->flags |= STOR_WANTS_FREE;
label->label = switch_end = new_label();
insn("jmp", label, NULL, "goto end of switch");
}
x86_statement(stmt->switch_statement);
if (stmt->switch_break->used)
emit_labelsym(stmt->switch_break, NULL);
if (switch_end)
emit_label(switch_end, NULL);
}
static void x86_struct_member(struct symbol *sym)
{
printf("\t%s:%d:%ld at offset %ld.%d", show_ident(sym->ident), sym->bit_size, sym->ctype.alignment, sym->offset, sym->bit_offset);
printf("\n");
}
static void x86_symbol(struct symbol *sym)
{
struct symbol *type;
if (!sym)
return;
type = sym->ctype.base_type;
if (!type)
return;
/*
* Show actual implementation information
*/
switch (type->type) {
case SYM_ARRAY:
if (sym->initializer)
emit_array(sym);
else
emit_array_noinit(sym);
break;
case SYM_BASETYPE:
if (sym->initializer) {
emit_object_pre(show_ident(sym->ident),
sym->ctype.modifiers,
sym->ctype.alignment,
sym->bit_size / 8);
emit_scalar(sym->initializer, sym->bit_size);
stor_sym_init(sym);
} else
emit_scalar_noinit(sym);
break;
case SYM_STRUCT:
case SYM_UNION: {
struct symbol *member;
printf(" {\n");
FOR_EACH_PTR(type->symbol_list, member) {
x86_struct_member(member);
} END_FOR_EACH_PTR(member);
printf("}\n");
break;
}
case SYM_FN: {
struct statement *stmt = type->stmt;
if (stmt) {
emit_func_pre(sym);
x86_statement(stmt);
emit_func_post(sym);
}
break;
}
default:
break;
}
if (sym->initializer && (type->type != SYM_BASETYPE) &&
(type->type != SYM_ARRAY)) {
printf(" = \n");
x86_expression(sym->initializer);
}
}
static void x86_symbol_init(struct symbol *sym);
static void x86_symbol_decl(struct symbol_list *syms)
{
struct symbol *sym;
FOR_EACH_PTR(syms, sym) {
x86_symbol_init(sym);
} END_FOR_EACH_PTR(sym);
}
static void loopstk_push(int cont_lbl, int loop_bottom_lbl)
{
struct function *f = current_func;
struct loop_stack *ls;
ls = malloc(sizeof(*ls));
ls->continue_lbl = cont_lbl;
ls->loop_bottom_lbl = loop_bottom_lbl;
ls->next = f->loop_stack;
f->loop_stack = ls;
}
static void loopstk_pop(void)
{
struct function *f = current_func;
struct loop_stack *ls;
assert(f->loop_stack != NULL);
ls = f->loop_stack;
f->loop_stack = f->loop_stack->next;
free(ls);
}
static int loopstk_break(void)
{
return current_func->loop_stack->loop_bottom_lbl;
}
static int loopstk_continue(void)
{
return current_func->loop_stack->continue_lbl;
}
static void emit_loop(struct statement *stmt)
{
struct statement *pre_statement = stmt->iterator_pre_statement;
struct expression *pre_condition = stmt->iterator_pre_condition;
struct statement *statement = stmt->iterator_statement;
struct statement *post_statement = stmt->iterator_post_statement;
struct expression *post_condition = stmt->iterator_post_condition;
int loop_top = 0, loop_bottom, loop_continue;
int have_bottom = 0;
struct storage *val;
loop_bottom = new_label();
loop_continue = new_label();
loopstk_push(loop_continue, loop_bottom);
x86_symbol_decl(stmt->iterator_syms);
x86_statement(pre_statement);
if (!post_condition || post_condition->type != EXPR_VALUE || post_condition->value) {
loop_top = new_label();
emit_label(loop_top, "loop top");
}
if (pre_condition) {
if (pre_condition->type == EXPR_VALUE) {
if (!pre_condition->value) {
struct storage *lbv;
lbv = new_storage(STOR_LABEL);
lbv->label = loop_bottom;
lbv->flags = STOR_WANTS_FREE;
insn("jmp", lbv, NULL, "go to loop bottom");
have_bottom = 1;
}
} else {
struct storage *lbv = new_storage(STOR_LABEL);
lbv->label = loop_bottom;
lbv->flags = STOR_WANTS_FREE;
have_bottom = 1;
val = x86_expression(pre_condition);
emit_move(val, REG_EAX, NULL, "loop pre condition");
insn("test", REG_EAX, REG_EAX, NULL);
insn("jz", lbv, NULL, NULL);
}
}
x86_statement(statement);
if (stmt->iterator_continue->used)
emit_label(loop_continue, "'continue' iterator");
x86_statement(post_statement);
if (!post_condition) {
struct storage *lbv = new_storage(STOR_LABEL);
lbv->label = loop_top;
lbv->flags = STOR_WANTS_FREE;
insn("jmp", lbv, NULL, "go to loop top");
} else if (post_condition->type == EXPR_VALUE) {
if (post_condition->value) {
struct storage *lbv = new_storage(STOR_LABEL);
lbv->label = loop_top;
lbv->flags = STOR_WANTS_FREE;
insn("jmp", lbv, NULL, "go to loop top");
}
} else {
struct storage *lbv = new_storage(STOR_LABEL);
lbv->label = loop_top;
lbv->flags = STOR_WANTS_FREE;
val = x86_expression(post_condition);
emit_move(val, REG_EAX, NULL, "loop post condition");
insn("test", REG_EAX, REG_EAX, NULL);
insn("jnz", lbv, NULL, NULL);
}
if (have_bottom || stmt->iterator_break->used)
emit_label(loop_bottom, "loop bottom");
loopstk_pop();
}
/*
* Print out a statement
*/
static struct storage *x86_statement(struct statement *stmt)
{
if (!stmt)
return NULL;
switch (stmt->type) {
default:
return NULL;
case STMT_RETURN:
return emit_return_stmt(stmt);
case STMT_DECLARATION:
x86_symbol_decl(stmt->declaration);
break;
case STMT_COMPOUND: {
struct statement *s;
struct storage *last = NULL;
FOR_EACH_PTR(stmt->stmts, s) {
last = x86_statement(s);
} END_FOR_EACH_PTR(s);
return last;
}
case STMT_EXPRESSION:
return x86_expression(stmt->expression);
case STMT_IF:
emit_if_conditional(stmt);
return NULL;
case STMT_CASE:
emit_case_statement(stmt);
break;
case STMT_SWITCH:
emit_switch_statement(stmt);
break;
case STMT_ITERATOR:
emit_loop(stmt);
break;
case STMT_NONE:
break;
case STMT_LABEL:
printf(".L%p:\n", stmt->label_identifier);
x86_statement(stmt->label_statement);
break;
case STMT_GOTO:
if (stmt->goto_expression) {
struct storage *val = x86_expression(stmt->goto_expression);
printf("\tgoto *v%d\n", val->pseudo);
} else if (!strcmp("break", show_ident(stmt->goto_label->ident))) {
struct storage *lbv = new_storage(STOR_LABEL);
lbv->label = loopstk_break();
lbv->flags = STOR_WANTS_FREE;
insn("jmp", lbv, NULL, "'break'; go to loop bottom");
} else if (!strcmp("continue", show_ident(stmt->goto_label->ident))) {
struct storage *lbv = new_storage(STOR_LABEL);
lbv->label = loopstk_continue();
lbv->flags = STOR_WANTS_FREE;
insn("jmp", lbv, NULL, "'continue'; go to loop top");
} else {
struct storage *labelsym = new_labelsym(stmt->goto_label);
insn("jmp", labelsym, NULL, NULL);
}
break;
case STMT_ASM:
printf("\tasm( .... )\n");
break;
}
return NULL;
}
static struct storage *x86_call_expression(struct expression *expr)
{
struct function *f = current_func;
struct symbol *direct;
struct expression *arg, *fn;
struct storage *retval, *fncall;
int framesize;
char s[64];
if (!expr->ctype) {
warning(expr->pos, "\tcall with no type!");
return NULL;
}
framesize = 0;
FOR_EACH_PTR_REVERSE(expr->args, arg) {
struct storage *new = x86_expression(arg);
int size = arg->ctype->bit_size;
/*
* FIXME: i386 SysV ABI dictates that values
* smaller than 32 bits should be placed onto
* the stack as 32-bit objects. We should not
* blindly do a 32-bit push on objects smaller
* than 32 bits.
*/
if (size < 32)
size = 32;
insn("pushl", new, NULL,
!framesize ? "begin function call" : NULL);
framesize += bits_to_bytes(size);
} END_FOR_EACH_PTR_REVERSE(arg);
fn = expr->fn;
/* Remove dereference, if any */
direct = NULL;
if (fn->type == EXPR_PREOP) {
if (fn->unop->type == EXPR_SYMBOL) {
struct symbol *sym = fn->unop->symbol;
if (sym->ctype.base_type->type == SYM_FN)
direct = sym;
}
}
if (direct) {
struct storage *direct_stor = new_storage(STOR_SYM);
direct_stor->flags |= STOR_WANTS_FREE;
direct_stor->sym = direct;
insn("call", direct_stor, NULL, NULL);
} else {
fncall = x86_expression(fn);
emit_move(fncall, REG_EAX, fn->ctype, NULL);
strcpy(s, "\tcall\t*%eax\n");
push_text_atom(f, s);
}
/* FIXME: pay attention to BITS_IN_POINTER */
if (framesize) {
struct storage *val = new_storage(STOR_VALUE);
val->value = (long long) framesize;
val->flags = STOR_WANTS_FREE;
insn("addl", val, REG_ESP, NULL);
}
retval = stack_alloc(4);
emit_move(REG_EAX, retval, NULL, "end function call");
return retval;
}
static struct storage *x86_address_gen(struct expression *expr)
{
struct function *f = current_func;
struct storage *addr;
struct storage *new;
char s[32];
addr = x86_expression(expr->unop);
if (expr->unop->type == EXPR_SYMBOL)
return addr;
emit_move(addr, REG_EAX, NULL, "begin deref ..");
/* FIXME: operand size */
strcpy(s, "\tmovl\t(%eax), %ecx\n");
push_text_atom(f, s);
new = stack_alloc(4);
emit_move(REG_ECX, new, NULL, ".... end deref");
return new;
}
static struct storage *x86_assignment(struct expression *expr)
{
struct expression *target = expr->left;
struct storage *val, *addr;
if (!expr->ctype)
return NULL;
val = x86_expression(expr->right);
addr = x86_address_gen(target);
switch (val->type) {
/* copy, where both operands are memory */
case STOR_PSEUDO:
case STOR_ARG:
emit_copy(addr, val, expr->ctype);
break;
/* copy, one or zero operands are memory */
case STOR_REG:
case STOR_SYM:
case STOR_VALUE:
case STOR_LABEL:
emit_move(val, addr, expr->left->ctype, NULL);
break;
case STOR_LABELSYM:
assert(0);
break;
}
return val;
}
static int x86_initialization(struct symbol *sym, struct expression *expr)
{
struct storage *val, *addr;
int bits;
if (!expr->ctype)
return 0;
bits = expr->ctype->bit_size;
val = x86_expression(expr);
addr = x86_symbol_expr(sym);
// FIXME! The "target" expression is for bitfield store information.
// Leave it NULL, which works fine.
emit_store(NULL, addr, val, bits);
return 0;
}
static struct storage *x86_access(struct expression *expr)
{
return x86_address_gen(expr);
}
static struct storage *x86_preop(struct expression *expr)
{
/*
* '*' is an lvalue access, and is fundamentally different
* from an arithmetic operation. Maybe it should have an
* expression type of its own..
*/
if (expr->op == '*')
return x86_access(expr);
if (expr->op == SPECIAL_INCREMENT || expr->op == SPECIAL_DECREMENT)
return emit_inc_dec(expr, 0);
return emit_regular_preop(expr);
}
static struct storage *x86_symbol_expr(struct symbol *sym)
{
struct storage *new = stack_alloc(4);
if (sym->ctype.modifiers & (MOD_TOPLEVEL | MOD_EXTERN | MOD_STATIC)) {
printf("\tmovi.%d\t\tv%d,$%s\n", bits_in_pointer, new->pseudo, show_ident(sym->ident));
return new;
}
if (sym->ctype.modifiers & MOD_ADDRESSABLE) {
printf("\taddi.%d\t\tv%d,vFP,$%lld\n", bits_in_pointer, new->pseudo, 0LL);
return new;
}
printf("\taddi.%d\t\tv%d,vFP,$offsetof(%s:%p)\n", bits_in_pointer, new->pseudo, show_ident(sym->ident), sym);
return new;
}
static void x86_symbol_init(struct symbol *sym)
{
struct symbol_private *priv = sym->aux;
struct expression *expr = sym->initializer;
struct storage *new;
if (expr)
new = x86_expression(expr);
else
new = stack_alloc(sym->bit_size / 8);
if (!priv) {
priv = calloc(1, sizeof(*priv));
sym->aux = priv;
/* FIXME: leak! we don't free... */
/* (well, we don't free symbols either) */
}
priv->addr = new;
}
static struct storage *x86_label_expr(struct expression *expr)
{
struct storage *new = stack_alloc(4);
printf("\tmovi.%d\t\tv%d,.L%p\n", bits_in_pointer, new->pseudo, expr->label_symbol);
return new;
}
static struct storage *x86_statement_expr(struct expression *expr)
{
return x86_statement(expr->statement);
}
static int x86_position_expr(struct expression *expr, struct symbol *base)
{
struct storage *new = x86_expression(expr->init_expr);
struct symbol *ctype = expr->init_expr->ctype;
printf("\tinsert v%d at [%d:%d] of %s\n", new->pseudo,
expr->init_offset, ctype->bit_offset,
show_ident(base->ident));
return 0;
}
static void x86_initializer_expr(struct expression *expr, struct symbol *ctype)
{
struct expression *entry;
FOR_EACH_PTR(expr->expr_list, entry) {
// Nested initializers have their positions already
// recursively calculated - just output them too
if (entry->type == EXPR_INITIALIZER) {
x86_initializer_expr(entry, ctype);
continue;
}
// Ignore initializer indexes and identifiers - the
// evaluator has taken them into account
if (entry->type == EXPR_IDENTIFIER || entry->type == EXPR_INDEX)
continue;
if (entry->type == EXPR_POS) {
x86_position_expr(entry, ctype);
continue;
}
x86_initialization(ctype, entry);
} END_FOR_EACH_PTR(entry);
}
/*
* Print out an expression. Return the pseudo that contains the
* variable.
*/
static struct storage *x86_expression(struct expression *expr)
{
if (!expr)
return NULL;
if (!expr->ctype) {
struct position *pos = &expr->pos;
printf("\tno type at %s:%d:%d\n",
stream_name(pos->stream),
pos->line, pos->pos);
return NULL;
}
switch (expr->type) {
default:
return NULL;
case EXPR_CALL:
return x86_call_expression(expr);
case EXPR_ASSIGNMENT:
return x86_assignment(expr);
case EXPR_COMPARE:
return emit_compare(expr);
case EXPR_BINOP:
case EXPR_COMMA:
case EXPR_LOGICAL:
return emit_binop(expr);
case EXPR_PREOP:
return x86_preop(expr);
case EXPR_POSTOP:
return emit_postop(expr);
case EXPR_SYMBOL:
return emit_symbol_expr_init(expr->symbol);
case EXPR_DEREF:
case EXPR_SIZEOF:
case EXPR_ALIGNOF:
warning(expr->pos, "invalid expression after evaluation");
return NULL;
case EXPR_CAST:
case EXPR_FORCE_CAST:
case EXPR_IMPLIED_CAST:
return emit_cast_expr(expr);
case EXPR_VALUE:
return emit_value(expr);
case EXPR_STRING:
return emit_string_expr(expr);
case EXPR_INITIALIZER:
x86_initializer_expr(expr, expr->ctype);
return NULL;
case EXPR_SELECT:
return emit_select_expr(expr);
case EXPR_CONDITIONAL:
return emit_conditional_expr(expr);
case EXPR_STATEMENT:
return x86_statement_expr(expr);
case EXPR_LABEL:
return x86_label_expr(expr);
// None of these should exist as direct expressions: they are only
// valid as sub-expressions of initializers.
case EXPR_POS:
warning(expr->pos, "unable to show plain initializer position expression");
return NULL;
case EXPR_IDENTIFIER:
warning(expr->pos, "unable to show identifier expression");
return NULL;
case EXPR_INDEX:
warning(expr->pos, "unable to show index expression");
return NULL;
case EXPR_TYPE:
warning(expr->pos, "unable to show type expression");
return NULL;
case EXPR_FVALUE:
warning(expr->pos, "floating point support is not implemented");
return NULL;
}
return NULL;
}