diff --git a/src/asm.h b/src/asm.h index 2d97fbd..0ab924a 100644 --- a/src/asm.h +++ b/src/asm.h @@ -6,7 +6,7 @@ #include #include -typedef size_t ip; +typedef uint32_t ip; extern ip here; /// Jump to a known address. @@ -15,17 +15,4 @@ void inst_jump(ip there); /// Jump to an unresolved address. ip inst_jump_unresolved(void); void inst_jump_resolve(ip disp, ip there); - -void inst_mov_imm(reg reg, uint64_t imm); -void inst_mov_imm_i64(reg reg, int64_t imm); -void inst_syscall(void); - -void inst_push(reg reg); -void inst_pop(reg reg); - -void inst_mov(reg dest, reg src); -void inst_mov_from(reg dest, reg base); -void inst_mov_from_disp(reg dest, reg base, int32_t disp); -void inst_mov_to(reg base, reg src); -void inst_mov_to_disp(reg base, reg src, int32_t disp); #endif diff --git a/src/io.c b/src/io.c index 6e42326..bd274dc 100644 --- a/src/io.c +++ b/src/io.c @@ -22,7 +22,7 @@ static const char* outfile_name; FILE* infile; FILE* outfile; // HACK: "here" tracking should be handled by the assembler, not IO. -size_t here = 0; +uint32_t here = 0; #ifdef __linux__ void open_files(const char* infile_name, const char* outfile_name_) { diff --git a/src/io.h b/src/io.h index abc5821..3818171 100644 --- a/src/io.h +++ b/src/io.h @@ -6,7 +6,7 @@ extern FILE* outfile; extern FILE* infile; -extern size_t here; +extern uint32_t here; void open_files(const char* infile_name, const char* outfile_name); void close_files(void); diff --git a/src/ir.c b/src/ir.c index 42b98f6..61f940c 100644 --- a/src/ir.c +++ b/src/ir.c @@ -12,174 +12,80 @@ #include #include -struct fixups { - struct fixups* next; - ip disp; +#define MAX_STACK_FRAMES 32 +#define MAX_LABELS 256 +#define MAX_FIXUPS 256 + +struct stack_frame { + uint32_t depth; + uint32_t label_depth; }; -struct label_info { - const struct frame* frame; - size_t argc; +struct label { + uint32_t frame; + uint32_t argc; ip definition; - struct fixups* fixups; + uint32_t fixupc; + ip fixups[MAX_FIXUPS]; }; -struct labels { - struct labels* prev; - struct label_info info; -}; +static uint32_t stack_depth = 0; +static uint32_t stack_frame = 0; +static struct stack_frame stack_frames[MAX_STACK_FRAMES]; +static uint32_t label_depth; +static struct label labels[MAX_LABELS]; -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; +void init(var* argc, var* argv, var* env) { + assert(stack_depth == 0); + x86_inst_mov_r64_r64(BP, SP); + // TODO: replace with add, once I implement add + x86_inst_add_r64_imm8(BP, 8 * 3); + *env = stack_depth++; + *argv = stack_depth++; + *argc = stack_depth++; } -// 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; - } +label enter(uint32_t retc) { + assert(stack_frame < MAX_STACK_FRAMES); + struct stack_frame frame = { stack_depth, label_depth }; + stack_frames[stack_frame] = frame; + stack_frame++; + return declare(retc); } -// Allocate registers or stack space for the arguments. -static void reserve(size_t argc) { - top_of_stack += argc; +void leave(var* args) { + assert(stack_frame > 0); + struct stack_frame frame = stack_frames[stack_frame]; + define(frame.label_depth, args); + stack_depth = frame.depth; + label_depth = frame.label_depth; } -static var new_var(void) { - var var = top_of_stack; - top_of_stack++; - x86_inst_push_r64(RA); - return var; +label declare(uint32_t argc) { + assert(label_depth < MAX_LABELS); + struct label label = { stack_frame, argc, (ip) -1, 0, 0 }; + labels[label_depth] = label; + return label_depth++; } -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) { - x86_inst_mov_r64_r64(ds.reg, ss.reg); - } else if (ds.type == STORE_STACK && ss.type == STORE_REG) { - x86_inst_mov_m64_r64_disp(RB, ss.reg, ds.off); - } else if (ds.type == STORE_REG && ds.type == STORE_STACK) { - x86_inst_mov_r64_m64_disp(ds.reg, RB, ss.off); - } else { - // FIXME: DI is scratch register? - x86_inst_mov_r64_m64_disp(DI, RB, ss.off); - x86_inst_mov_m64_r64_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); +void define(label l, var* args) { + struct label* label = &labels[l]; 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); + while (label->fixupc > 0) { + label->fixupc--; + inst_jump_resolve(label->fixups[label->fixupc], here); } - 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); +void jump(label l, var* args) { + struct label* label = &labels[l]; + if (label->definition == (ip) -1) { + assert(label->fixupc < MAX_FIXUPS); + label->fixups[label->fixupc] = inst_jump_unresolved(); + label->fixupc++; } else { - ip disp = inst_jump_unresolved(); - queue_fixup(label, disp); + inst_jump(label->definition); } - leave(); } void jump_table(size_t branches, label* labels, var index, var* args) { @@ -190,34 +96,59 @@ void jump_if(label label, var cond, var* args) { assert(0); // UNIMPLEMENTED } - -static void save(var* vars) { - +void load_var(reg reg, var var) { + // the stack grows downward, so the bottom of the stack, BP, points to nothing; + // subtracting 8 causes it to point to the first variable, 0. + // (each variable is 8 bytes.) + x86_inst_mov_r64_m64_disp(reg, BP, -(var * 8) - 8); } -var lit(uint64_t x) { - var var = new_var(); - struct storage stg = storage(var); - assert(stg.type == STORE_REG); - x86_inst_mov_r64_imm(stg.reg, x); - return var; +var push_var(reg reg) { + x86_inst_push_r64(reg); + return stack_depth++; } -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) - x86_inst_mov_r64_m64_disp(DI, RB, -args[1] * 8 - 16); - if (argc > 2) - x86_inst_mov_r64_m64_disp(SI, RB, -args[2] * 8 - 16); - if (argc > 3) - x86_inst_mov_r64_m64_disp(RD, RB, -args[3] * 8 - 16); +var lit(uint64_t lit) { + x86_inst_mov_r64_imm(AX, lit); + x86_inst_push_r64(AX); + return stack_depth++; +} + +var sub(var subtrahend, var minuend) { + // TODO: use modr/m + load_var(AX, subtrahend); + load_var(BX, minuend); + x86_inst_sub_r64_r64(AX, BX); + return push_var(AX); +} + +// Linux system call: https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/ +var syscall(size_t argc, var* args) { + assert(argc > 0 && argc <= 7); + switch(argc) { + case 7: + load_var(R9, args[6]); + __attribute__((fallthrough)); + case 6: + load_var(R8, args[5]); + __attribute__ ((fallthrough)); + case 5: + load_var(R10, args[4]); + __attribute__ ((fallthrough)); + case 4: + load_var(DX, args[3]); + __attribute__ ((fallthrough)); + case 3: + load_var(SI, args[2]); + __attribute__ ((fallthrough)); + case 2: + load_var(DI, args[1]); + __attribute__ ((fallthrough)); + case 1: + // the system call number, not an argument + load_var(AX, args[0]); + } + // NOTE: syscall clobbers rcx and r11. x86_inst_syscall(); -} - -void init(void) { - x86_inst_mov_r64_r64(RB, SP); + return push_var(AX); } diff --git a/src/ir.h b/src/ir.h index beb0749..cd61831 100644 --- a/src/ir.h +++ b/src/ir.h @@ -1,49 +1,71 @@ -#ifndef _ASSEMBLER_H -#define _ASSEMBLER_H +#ifndef _IR_H +#define _IR_H #include #include -typedef size_t var; -typedef struct label_info* label; +typedef uint32_t var; +typedef uint32_t label; -/// Declare a new label. -/// -/// A label is the destination of a jump, -/// located with a fixed stack context and fixed argument types. -label declare_label(size_t args); +/// Call this at the beginning of execution. +/// It performs initialization and stuff. +void init(var* argc, var* argv, var* env); -/// Define a previously-declared label. +/// Enter a new block. /// -/// The label must be defined in the same stack context -/// which it was declared in. +/// All labels defined in this block will have access to all variables +/// which are in scope as of calling `enter`. You will be able to jump +/// to any label which is defined in this block from here +/// to the symmetric `leave`. /// -/// The definition of the label is... here, which is to say -/// whatever code you proceed to generate after this. -/// -/// A label is implicitly terminated by an unconditional jump or exit. -/// However, it may exit at multiple locations via unconditional jumps. -void define_label(label label, var* args); +/// This also generates a new label corresponding with the end of the block, +/// which will be automatically defined when you call `leave`. +label enter(uint32_t retc); -/// Jump to label, unconditionally. Terminates a block. +/// Leave a block. /// -/// It is only possible to jump to a label in a parent or adjacent stack frame -/// (you can't jump *deeper* into the stack). +/// This will restore the context to how it was when `enter` was called, +/// plus the return values declared by the call to `enter`. +void leave(var* args); + + +/// Declare a new label in the innermost block. +/// +/// This label can only be called from the block or nested blocks. +/// This label must be called with the given number of arguments. +label declare(uint32_t argc); + +/// Define a label in the innermost block, automatically terminating +/// any previous labels. +/// +/// All variables defined prior to the beginning of this block will be in scope. +/// The arguments associated with the label will be in scope. +/// Variables defined *after* the beginning of the block but *prior* to this label +/// will *not* be in scope. +/// +/// From this label you can jump to any label in the enclosing block +/// or any parent block. +void define(label label, var* args); + +/// Jump to label, unconditionally; never returns. void jump(label label, var* args); -/// Jump to label in table; never returns. Terminates a block. +/// Jump to `index`th label in table; never returns. /// /// All labels must be at the same depth and accept the same arguments. /// `index` must not be out of bounds. void jump_table(size_t branches, label* labels, var index, var* args); -/// Jump to label if `cond` is not zero. Does not terminate a block. +/// Jump to label if `cond` is not zero. void jump_if(label label, var cond, var* args); -var lit(uint64_t x); +/// Integer literal. +var lit(uint64_t lit); -void syscall(size_t argc, var* args); +/// Subtraction. +var sub(var subtrahend, var minuend); -void init(void); +/// Perform a system call. +var syscall(size_t argc, var* args); #endif diff --git a/src/lex.c b/src/lex.c new file mode 100644 index 0000000..e69de29 diff --git a/src/main.c b/src/main.c index 8067981..f0f6601 100644 --- a/src/main.c +++ b/src/main.c @@ -14,10 +14,13 @@ #define ELF_HEADER_SIZE 0xb0 size_t compile(void) { - init(); - var code = lit(42); - var call = lit(60); - var args[2] = { call, code }; + var argc, argv, env; + init(&argc, &argv, &env); + var a = lit(52); + var b = lit(10); + var exit_code = sub(a, b); + var sys_exit = lit(60); + var args[2] = { sys_exit, exit_code }; syscall(2, args); return ELF_HEADER_SIZE; } diff --git a/src/x86encode.c b/src/x86encode.c index 695393d..6c42370 100644 --- a/src/x86encode.c +++ b/src/x86encode.c @@ -36,6 +36,12 @@ static void x86_rexwr(reg reg) { emit_u8(rex); } +static void x86_rexwb(reg b) { + uint8_t rex = REX | REX_W; + if (b >= R8) rex |= REX_B; + emit_u8(rex); +} + static void x86_rexwrb(reg r, reg b) { uint8_t rex = REX | REX_W; if (r >= R8) rex |= REX_R; @@ -59,6 +65,10 @@ static void x86_modrm32(reg r, reg b) { emit_u8(MODRM_RM32 | (REG(r) << 3) | REG(b)); } +static void x86_modxm(uint8_t ext, reg b) { + emit_u8(MODRM_RR | (ext << 3) | REG(b)); +} + static void x86_enc_opr(uint8_t op, reg reg) { x86_opt_rexr(reg); emit_u8(op + REG(reg)); @@ -123,6 +133,28 @@ static void x86_enc_rexw_modrmd(uint8_t op, reg r, reg b, int32_t disp) { } } +static void x86_enc_rexw_modxm_imm8(uint8_t op, uint8_t ext, reg m, uint8_t imm) { + x86_rexwb(m); + emit_u8(op); + x86_modxm(ext, m); + emit_u8(imm); +} + +static void x86_enc_rexw_modxm_imm32(uint8_t op, uint8_t ext, reg m, uint32_t imm) { + x86_rexwb(m); + emit_u8(op); + x86_modxm(ext, m); + emit_u32(imm); +} + +static void x86_enc_rexw_modxm_imm(uint8_t op, uint8_t ext, reg m, uint32_t imm) { + if (imm <= UINT8_MAX) { + x86_enc_rexw_modxm_imm8(op, ext, m, (uint8_t) imm); + } else { + x86_enc_rexw_modxm_imm32(op, ext, m, imm); + } +} + static void x86_enc_disp8(uint8_t op, int8_t disp) { uint8_t buf[2] = { op, (uint8_t) disp }; emit(buf, 2); @@ -142,7 +174,7 @@ void x86_inst_mov_r64_imm32(reg dest, uint32_t imm) { } void x86_inst_mov_r64_imm(reg dest, uint64_t imm) { - // TODO: xor if 0, use inc and dec, 16-bit and 8-bit immediates + // TODO: xor if 0, use inc and dec, 16-bit and 8-bit immediates, sign extension if (imm <= UINT32_MAX) { x86_inst_mov_r64_imm32(dest, (uint32_t) imm); } else { @@ -150,15 +182,6 @@ void x86_inst_mov_r64_imm(reg dest, uint64_t imm) { } } -void x86_inst_mov_r64_imms(reg dest, int64_t imm) { - // TODO: sign-extend - if (imm >= 0) { - x86_inst_mov_r64_imm(dest, imm); - } else { - x86_inst_mov_r64_imm64(dest, (int64_t) imm); - } -} - void x86_inst_mov_r64_r64(reg dest, reg src) { x86_enc_rexw_modrr(0x8b, dest, src); } @@ -220,6 +243,28 @@ void x86_inst_jmp_disp(int32_t disp) { } } +// TODO: special instructions for AX +void x86_inst_sub_r64_imm8(reg dest, int8_t imm) { + x86_enc_rexw_modxm_imm8(0x83, 5, dest, (uint8_t) imm); +} + +void x86_inst_sub_r64_imm32(reg dest, int32_t imm) { + x86_enc_rexw_modxm_imm32(0x81, 5, dest, (uint32_t) imm); +} + +void x86_inst_sub_r64_imm(reg dest, int32_t imm) { + x86_enc_rexw_modxm_imm(0x81, 5, dest, (uint32_t) imm); +} + +void x86_inst_sub_r64_r64(reg dest, reg src) { + x86_enc_rexw_modrr(0x2B, dest, src); +} + +// TODO: in case of -128, choice of add vs. sub matters! +void x86_inst_add_r64_imm8(reg dest, int8_t imm) { + x86_enc_rexw_modxm_imm8(0x83, 0, dest, (uint8_t) imm); +} + void x86_inst_syscall(void) { const uint8_t buf[2] = { 0x0f, 0x05 }; emit(buf, 2); diff --git a/src/x86encode.h b/src/x86encode.h index ef203d7..41dbd60 100644 --- a/src/x86encode.h +++ b/src/x86encode.h @@ -7,10 +7,10 @@ // The specific register size (e.g. al/ax/eax/rax) depends on the instruction. // All registers are valid for all instructions; we will perform exchanges if necessary. typedef enum reg { - RA = 0, // rax, eax, ax, al - RC = 1, // rcx, ecx, cx, cl - RD = 2, // rdx, edx, dx, dl - RB = 3, // rbx, ebx, bx, bl + AX = 0, // rax, eax, ax, al + CX = 1, // rcx, ecx, cx, cl + DX = 2, // rdx, edx, dx, dl + BX = 3, // rbx, ebx, bx, bl SP = 4, // rsp, esp, sp, spl (we do not use ah) BP = 5, // rbp, ebp, bp, bpl (we do not use ch) SI = 6, // rsi, esi, si, sil (we do not use dh) @@ -29,7 +29,6 @@ typedef enum reg { void x86_inst_mov_r64_imm64(reg dest, uint64_t imm); void x86_inst_mov_r64_imm32(reg dest, uint32_t imm); void x86_inst_mov_r64_imm(reg dest, uint64_t imm); -void x86_inst_mov_r64_imms(reg dest, int64_t imm); void x86_inst_mov_r64_r64(reg dest, reg src); void x86_inst_mov_r64_m64(reg dest, reg src); void x86_inst_mov_r64_m64_disp8(reg dest, reg src, int8_t disp); @@ -39,13 +38,23 @@ void x86_inst_mov_m64_r64(reg dest, reg src); void x86_inst_mov_m64_r64_disp8(reg dest, reg src, int8_t disp); void x86_inst_mov_m64_r64_disp32(reg dest, reg src, int32_t disp); void x86_inst_mov_m64_r64_disp(reg dest, reg src, int32_t disp); + void x86_inst_push_r64(reg reg); void x86_inst_pop_r64(reg reg); + #define X86_JMP_DISP8_SIZE 2 void x86_inst_jmp_disp8(int8_t disp); #define X86_JMP_DISP32_SIZE 5 void x86_inst_jmp_disp32(int32_t disp); void x86_inst_jmp_disp(int32_t disp); + +void x86_inst_sub_r64_imm8(reg dest, int8_t imm); +void x86_inst_sub_r64_imm32(reg dest, int32_t imm); +void x86_inst_sub_r64_imm(reg dest, int32_t imm); +void x86_inst_sub_r64_r64(reg dest, reg src); + +void x86_inst_add_r64_imm8(reg dest, int8_t imm); + void x86_inst_syscall(void); #endif