pass-lang/src/ir.c

169 lines
4.3 KiB
C

/// This file serves conceptually as the intermediate representation (IR)
/// of the compiler. Compared to "asm", this file is aware of stack frames,
/// control flow blocks and labels, compound types like structs and enums,
/// and register allocation.
#include "asm.h"
#include "ir.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 {
uint32_t frame;
uint32_t argc;
ip definition;
uint32_t fixupc;
ip fixups[MAX_FIXUPS];
};
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];
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++;
}
void enter(void) {
assert(stack_frame < MAX_STACK_FRAMES);
struct stack_frame frame = { stack_depth, label_depth };
stack_frames[stack_frame] = frame;
stack_frame++;
// exit label
declare(0);
}
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;
}
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++;
}
label declare_exit(uint32_t argc) {
label label = stack_frames[stack_frame].label_depth;
labels[label].argc = argc;
return label;
}
void define(label l, var* args) {
struct label* label = &labels[l];
label->definition = here;
while (label->fixupc > 0) {
label->fixupc--;
inst_jump_resolve(label->fixups[label->fixupc], here);
}
}
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 {
inst_jump(label->definition);
}
}
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
}
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 push_var(reg reg) {
x86_inst_push_r64(reg);
return stack_depth++;
}
var lit(uint64_t lit) {
x86_inst_mov_r64_imm(AX, lit);
x86_inst_push_r64(AX);
return stack_depth++;
}
var lit_string(char* str) {
fprintf(stderr, "error: string literals not yet implemented\n");
exit(1);
}
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();
return push_var(AX);
}