220 lines
5.4 KiB
C
220 lines
5.4 KiB
C
#include "asm.h"
|
|
#include "ir.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
struct fixups {
|
|
struct fixups* next;
|
|
ip disp;
|
|
};
|
|
|
|
struct label_info {
|
|
const struct frame* frame;
|
|
size_t argc;
|
|
ip definition;
|
|
struct fixups* fixups;
|
|
};
|
|
|
|
struct labels {
|
|
struct labels* prev;
|
|
struct label_info info;
|
|
};
|
|
|
|
struct frame {
|
|
struct frame* prev;
|
|
size_t depth;
|
|
struct labels* labels;
|
|
};
|
|
static struct frame* current_frame;
|
|
|
|
struct storage {
|
|
enum { STORE_REG, STORE_STACK } type;
|
|
union { reg reg; size_t off; };
|
|
};
|
|
|
|
static size_t top_of_stack;
|
|
|
|
// Enter a new stack frame.
|
|
static void enter(void) {
|
|
struct frame* next = malloc(sizeof(struct frame));
|
|
next->prev = current_frame;
|
|
next->depth = top_of_stack;
|
|
current_frame = next;
|
|
}
|
|
|
|
// Leave the current stack frame.
|
|
static void leave(void) {
|
|
struct frame* next = current_frame;
|
|
current_frame = next->prev;
|
|
top_of_stack = next->depth;
|
|
struct labels* labels = next->labels;
|
|
free(next);
|
|
while (labels != NULL) {
|
|
struct labels* next_label = labels->prev;
|
|
free(labels);
|
|
labels = next_label;
|
|
}
|
|
}
|
|
|
|
// Allocate registers or stack space for the arguments.
|
|
static void reserve(size_t argc) {
|
|
top_of_stack += argc;
|
|
}
|
|
|
|
static var new_var(void) {
|
|
var var = top_of_stack;
|
|
top_of_stack++;
|
|
inst_push(RA);
|
|
return var;
|
|
}
|
|
|
|
static struct storage storage(var var) {
|
|
struct storage storage;
|
|
if (var == top_of_stack - 1) {
|
|
storage.type = STORE_REG;
|
|
storage.reg = RA;
|
|
} else {
|
|
storage.type = STORE_STACK;
|
|
storage.off = -var * 8 - 16;
|
|
}
|
|
return storage;
|
|
}
|
|
|
|
static void move(var dest, var src) {
|
|
if (dest == src) return;
|
|
struct storage ds = storage(dest);
|
|
struct storage ss = storage(src);
|
|
if (ds.type == STORE_REG && ss.type == STORE_REG) {
|
|
inst_mov(ds.reg, ss.reg);
|
|
} else if (ds.type == STORE_STACK && ss.type == STORE_REG) {
|
|
inst_mov_to_disp(RB, ss.reg, ds.off);
|
|
} else if (ds.type == STORE_REG && ds.type == STORE_STACK) {
|
|
inst_mov_from_disp(ds.reg, RB, ss.off);
|
|
} else {
|
|
// FIXME: DI is scratch register?
|
|
inst_mov_from_disp(DI, RB, ss.off);
|
|
inst_mov_to_disp(RB, DI, ds.off);
|
|
}
|
|
}
|
|
|
|
static void exchange(var x, var y) {
|
|
if (x == y) return;
|
|
assert(0); // UNIMPLEMENTED
|
|
}
|
|
|
|
// Restore the stack and registers to a previous frame,
|
|
// in preparation for a jump out of the current frame.
|
|
//
|
|
// This involves loading spilled variables into registers,
|
|
// restoring the stack pointer,
|
|
// spilling variables onto the stack to make space for arguments,
|
|
// and relocating arguments to the correct registers.
|
|
static void restore(const struct frame* frame, size_t argc, var* args) {
|
|
for (size_t i = 0; i < argc; i++) {
|
|
var arg = args[i];
|
|
size_t depth = frame->depth + i;
|
|
if (arg == depth) continue;
|
|
size_t conflict = (size_t) -1;
|
|
for (size_t j = i + 1; j < argc; j++) {
|
|
if (depth == args[j]) {
|
|
conflict = j;
|
|
break;
|
|
}
|
|
}
|
|
if (conflict == (size_t) -1) {
|
|
move(depth, args[i]);
|
|
} else {
|
|
// TODO: an algorithm which produces fewer exchanges
|
|
exchange(args[conflict], args[i]);
|
|
args[conflict] = args[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
label declare_label(size_t argc) {
|
|
struct labels* labels = malloc(sizeof(struct labels));
|
|
labels->prev = current_frame->labels;
|
|
labels->info.frame = current_frame;
|
|
labels->info.argc = argc;
|
|
labels->info.definition = (ip) -1;
|
|
current_frame->labels = labels;
|
|
return &labels->info;
|
|
}
|
|
|
|
void define_label(label label, var* args) {
|
|
assert(label->frame == current_frame);
|
|
label->definition = here;
|
|
struct fixups* fixups = label->fixups;
|
|
while (fixups != NULL) {
|
|
struct fixups* fixup = fixups;
|
|
inst_jump_resolve(fixup->disp, here);
|
|
fixups = fixup->next;
|
|
free(fixup);
|
|
}
|
|
enter();
|
|
reserve(label->argc);
|
|
}
|
|
|
|
void queue_fixup(label label, ip disp) {
|
|
struct fixups* fixup = malloc(sizeof(struct fixups));
|
|
fixup->next = label->fixups;
|
|
fixup->disp = disp;
|
|
label->fixups = fixup;
|
|
}
|
|
|
|
void jump(label label, var* args) {
|
|
restore(label->frame, label->argc, args);
|
|
if (label->definition != (ip) -1) {
|
|
inst_jump(label->definition);
|
|
} else {
|
|
ip disp = inst_jump_unresolved();
|
|
queue_fixup(label, disp);
|
|
}
|
|
leave();
|
|
}
|
|
|
|
void jump_table(size_t branches, label* labels, var index, var* args) {
|
|
assert(0); // UNIMPLEMENTED
|
|
}
|
|
|
|
void jump_if(label label, var cond, var* args) {
|
|
assert(0); // UNIMPLEMENTED
|
|
}
|
|
|
|
|
|
static void save(var* vars) {
|
|
|
|
}
|
|
|
|
var lit(uint64_t x) {
|
|
var var = new_var();
|
|
struct storage stg = storage(var);
|
|
assert(stg.type == STORE_REG);
|
|
inst_mov_imm(stg.reg, x);
|
|
return var;
|
|
}
|
|
|
|
void syscall(size_t argc, var* args) {
|
|
assert(argc > 0);
|
|
// rax already populated by top of stack
|
|
// FIXME: this won't work forever
|
|
// FIXME: save args in case we don't want to sysexit
|
|
// FIXME: save other registers
|
|
if (argc > 1)
|
|
inst_mov_from_disp(DI, RB, -args[1] * 8 - 16);
|
|
if (argc > 2)
|
|
inst_mov_from_disp(SI, RB, -args[2] * 8 - 16);
|
|
if (argc > 3)
|
|
inst_mov_from_disp(RD, RB, -args[3] * 8 - 16);
|
|
inst_syscall();
|
|
}
|
|
|
|
void init(void) {
|
|
inst_mov(RB, SP);
|
|
}
|