init
This commit is contained in:
commit
e2c5f1c66e
14
.clang-format
Normal file
14
.clang-format
Normal file
@ -0,0 +1,14 @@
|
||||
Language: Cpp
|
||||
BasedOnStyle: WebKit
|
||||
IndentWidth: 4
|
||||
ColumnLimit: 80
|
||||
IndentCaseLabels: true
|
||||
InsertNewlineAtEOF: true
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
|
||||
BinPackArguments: false
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
|
||||
BinPackParameters: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
build/
|
||||
.vscode/
|
||||
25
Makefile
Normal file
25
Makefile
Normal file
@ -0,0 +1,25 @@
|
||||
CC = gcc
|
||||
|
||||
CFLAGS = -std=c23 \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-pedantic-errors \
|
||||
-g \
|
||||
-fsanitize=address
|
||||
|
||||
BUILD_DIR = build
|
||||
TARGET = $(BUILD_DIR)/main
|
||||
SRC = main.c parse.c ir.c arena.c codegen_x86.c jit_x86.c
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(BUILD_DIR):
|
||||
mkdir -p $(BUILD_DIR)
|
||||
|
||||
$(TARGET): $(SRC) | $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) $(SRC) -o $(TARGET)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
|
||||
.PHONY: all clean
|
||||
43
arena.c
Normal file
43
arena.c
Normal file
@ -0,0 +1,43 @@
|
||||
#include "arena.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void arena_init(Arena* a)
|
||||
{
|
||||
a->data = NULL;
|
||||
a->size = 0;
|
||||
a->capacity = 0;
|
||||
}
|
||||
|
||||
void* arena_alloc(Arena* a, size_t size)
|
||||
{
|
||||
// simple alignment to 8 bytes
|
||||
size = (size + 7) & ~((size_t)7);
|
||||
|
||||
if (a->size + size > a->capacity) {
|
||||
size_t new_cap = a->capacity ? a->capacity * 2 : 1024;
|
||||
|
||||
while (new_cap < a->size + size)
|
||||
new_cap *= 2;
|
||||
|
||||
uint8_t* new_data = realloc(a->data, new_cap);
|
||||
if (!new_data)
|
||||
return NULL;
|
||||
|
||||
a->data = new_data;
|
||||
a->capacity = new_cap;
|
||||
}
|
||||
|
||||
void* ptr = a->data + a->size;
|
||||
a->size += size;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void arena_free(Arena* a)
|
||||
{
|
||||
free(a->data);
|
||||
a->data = NULL;
|
||||
a->size = 0;
|
||||
a->capacity = 0;
|
||||
}
|
||||
18
arena.h
Normal file
18
arena.h
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef ARENA_H
|
||||
#define ARENA_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t* data;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
} Arena;
|
||||
|
||||
void arena_init(Arena* a);
|
||||
void* arena_alloc(Arena* a, size_t size);
|
||||
void arena_free(Arena* a);
|
||||
|
||||
#endif
|
||||
517
codegen_x86.c
Normal file
517
codegen_x86.c
Normal file
@ -0,0 +1,517 @@
|
||||
#include "codegen_x86.h"
|
||||
#include "ir.h"
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void cg_block_init(CgBlock* b)
|
||||
{
|
||||
arena_init(&b->arena);
|
||||
|
||||
b->count = 0;
|
||||
b->capacity = 64;
|
||||
b->insts = arena_alloc(&b->arena, b->capacity * sizeof(CgInst*));
|
||||
|
||||
b->vreg_map = NULL;
|
||||
|
||||
b->next_vreg = 1;
|
||||
}
|
||||
|
||||
void cg_block_free(CgBlock* b)
|
||||
{
|
||||
arena_free(&b->arena);
|
||||
if (b->vreg_map)
|
||||
free(b->vreg_map);
|
||||
}
|
||||
|
||||
static const char* phys_reg_name(PhysReg r)
|
||||
{
|
||||
switch (r) {
|
||||
case RAX:
|
||||
return "rax";
|
||||
case RDX:
|
||||
return "rdx";
|
||||
case RCX:
|
||||
return "rcx";
|
||||
case RBX:
|
||||
return "rbx";
|
||||
case RSI:
|
||||
return "rsi";
|
||||
case RDI:
|
||||
return "rdi";
|
||||
case R8:
|
||||
return "r8";
|
||||
case R9:
|
||||
return "r9";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static void print_vreg(VReg v)
|
||||
{
|
||||
printf("v%u", v);
|
||||
}
|
||||
|
||||
void cg_block_print_vreg(const CgBlock* block)
|
||||
{
|
||||
printf("=== CgBlock (VREG view, %zu insts) ===\n", block->count);
|
||||
|
||||
for (size_t i = 0; i < block->count; i++) {
|
||||
CgInst* inst = block->insts[i];
|
||||
|
||||
printf("%zu: ", i);
|
||||
|
||||
switch (inst->op) {
|
||||
|
||||
case CG_IMM64:
|
||||
print_vreg(inst->dst);
|
||||
printf(" = IMM64 %llu\n", (unsigned long long)inst->imm);
|
||||
break;
|
||||
|
||||
case CG_ADD8:
|
||||
print_vreg(inst->dst);
|
||||
printf(" = ADD8 ");
|
||||
print_vreg(inst->binop.lhs);
|
||||
printf(", ");
|
||||
print_vreg(inst->binop.rhs);
|
||||
printf("\n");
|
||||
break;
|
||||
|
||||
case CG_SUB8:
|
||||
print_vreg(inst->dst);
|
||||
printf(" = SUB8 ");
|
||||
print_vreg(inst->binop.lhs);
|
||||
printf(", ");
|
||||
print_vreg(inst->binop.rhs);
|
||||
printf("\n");
|
||||
break;
|
||||
|
||||
case CG_MUL8:
|
||||
print_vreg(inst->dst);
|
||||
printf(" = MUL8 ");
|
||||
print_vreg(inst->binop.lhs);
|
||||
printf(", ");
|
||||
print_vreg(inst->binop.rhs);
|
||||
printf("\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("???\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
printf("=====================================\n");
|
||||
}
|
||||
|
||||
void cg_block_print_phys(const CgBlock* block)
|
||||
{
|
||||
printf("=== CgBlock (PHYS REG view, %zu insts) ===\n", block->count);
|
||||
|
||||
for (size_t i = 0; i < block->count; i++) {
|
||||
CgInst* inst = block->insts[i];
|
||||
|
||||
printf("%zu: ", i);
|
||||
|
||||
switch (inst->op) {
|
||||
|
||||
case CG_IMM64:
|
||||
printf("%s = IMM64 %llu\n",
|
||||
phys_reg_name(block->vreg_map[inst->dst].reg),
|
||||
(unsigned long long)inst->imm);
|
||||
break;
|
||||
|
||||
case CG_ADD8:
|
||||
printf("%s = ADD8 %s, %s\n",
|
||||
phys_reg_name(block->vreg_map[inst->dst].reg),
|
||||
phys_reg_name(block->vreg_map[inst->binop.lhs].reg),
|
||||
phys_reg_name(block->vreg_map[inst->binop.rhs].reg));
|
||||
break;
|
||||
|
||||
case CG_SUB8:
|
||||
printf("%s = SUB8 %s, %s\n",
|
||||
phys_reg_name(block->vreg_map[inst->dst].reg),
|
||||
phys_reg_name(block->vreg_map[inst->binop.lhs].reg),
|
||||
phys_reg_name(block->vreg_map[inst->binop.rhs].reg));
|
||||
break;
|
||||
|
||||
case CG_MUL8:
|
||||
printf("%s = MUL8 %s, %s\n",
|
||||
phys_reg_name(block->vreg_map[inst->dst].reg),
|
||||
phys_reg_name(block->vreg_map[inst->binop.lhs].reg),
|
||||
phys_reg_name(block->vreg_map[inst->binop.rhs].reg));
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("? = UNKNOWN OP %d\n", inst->op);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
printf("========================================\n");
|
||||
}
|
||||
|
||||
static void cg_grow(CgBlock* b)
|
||||
{
|
||||
size_t new_cap = b->capacity * 2;
|
||||
|
||||
CgInst** new_arr = arena_alloc(&b->arena, new_cap * sizeof(CgInst*));
|
||||
|
||||
memcpy(new_arr, b->insts, b->count * sizeof(CgInst*));
|
||||
|
||||
b->insts = new_arr;
|
||||
b->capacity = new_cap;
|
||||
}
|
||||
|
||||
static CgInst* cg_emit(CgBlock* b)
|
||||
{
|
||||
if (b->count == b->capacity) {
|
||||
cg_grow(b);
|
||||
}
|
||||
|
||||
CgInst* inst = arena_alloc(&b->arena, sizeof(CgInst));
|
||||
b->insts[b->count++] = inst;
|
||||
|
||||
return inst;
|
||||
}
|
||||
|
||||
void ir_block_isel_x86(CgBlock* cg, const IrBlock* ir)
|
||||
{
|
||||
cg->count = 0;
|
||||
cg->next_vreg = 1;
|
||||
|
||||
cg->vreg_map_size = ir->next_vreg;
|
||||
cg->vreg_map = calloc(cg->vreg_map_size, sizeof(RegMap));
|
||||
|
||||
for (size_t i = 0; i < ir->count; i++) {
|
||||
IrInst* inst = ir->insts[i];
|
||||
|
||||
switch (inst->op) {
|
||||
|
||||
case OP_INT: {
|
||||
CgInst* out = cg_emit(cg);
|
||||
out->op = CG_IMM64;
|
||||
out->dst = inst->vreg;
|
||||
out->imm = inst->value;
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_ADD: {
|
||||
CgInst* out = cg_emit(cg);
|
||||
out->op = CG_ADD8;
|
||||
out->dst = inst->vreg;
|
||||
|
||||
out->binop.lhs = inst->operands[0]->vreg;
|
||||
out->binop.rhs = inst->operands[1]->vreg;
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SUB: {
|
||||
CgInst* out = cg_emit(cg);
|
||||
out->op = CG_SUB8;
|
||||
out->dst = inst->vreg;
|
||||
|
||||
out->binop.lhs = inst->operands[0]->vreg;
|
||||
out->binop.rhs = inst->operands[1]->vreg;
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_MUL: {
|
||||
CgInst* out = cg_emit(cg);
|
||||
out->op = CG_MUL8;
|
||||
out->dst = inst->vreg;
|
||||
|
||||
out->binop.lhs = inst->operands[0]->vreg;
|
||||
out->binop.rhs = inst->operands[1]->vreg;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
cg->result_vreg = inst->vreg;
|
||||
}
|
||||
}
|
||||
|
||||
static PhysReg phys_alloc(VReg vreg, RegMap* map, size_t size)
|
||||
{
|
||||
// 1. reuse if already assigned
|
||||
if (vreg < size && map[vreg].assigned)
|
||||
return map[vreg].reg;
|
||||
|
||||
// 2. find free register
|
||||
static PhysReg next = RAX;
|
||||
|
||||
for (int i = 0; i < REG_COUNT; i++) {
|
||||
PhysReg r = (next + i) % REG_COUNT;
|
||||
|
||||
int used = 0;
|
||||
for (size_t j = 0; j < size; j++) {
|
||||
if (map[j].assigned && map[j].reg == r) {
|
||||
used = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!used) {
|
||||
next = (r + 1) % REG_COUNT;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback (no spilling implemented yet)
|
||||
return RAX;
|
||||
}
|
||||
|
||||
void cg_block_regalloc_x86(CgBlock* block)
|
||||
{
|
||||
size_t n = block->vreg_map_size;
|
||||
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
block->vreg_map[i].assigned = 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < block->count; i++) {
|
||||
CgInst* inst = block->insts[i];
|
||||
|
||||
phys_alloc(inst->dst, block->vreg_map, n);
|
||||
|
||||
if (inst->op == CG_ADD8 || inst->op == CG_SUB8 || inst->op == CG_MUL8) {
|
||||
|
||||
phys_alloc(inst->binop.lhs, block->vreg_map, n);
|
||||
phys_alloc(inst->binop.rhs, block->vreg_map, n);
|
||||
}
|
||||
}
|
||||
|
||||
// enforce result vreg → RAX
|
||||
VReg r = block->result_vreg;
|
||||
|
||||
if (r < n) {
|
||||
block->vreg_map[r].reg = RAX;
|
||||
block->vreg_map[r].assigned = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void test_ir_block_isel_add(void)
|
||||
{
|
||||
IrBlock ir;
|
||||
ir_block_init(&ir);
|
||||
|
||||
// IR: 2 + 3
|
||||
|
||||
Expr two = { .type = EXPR_INT, .text = "2" };
|
||||
Expr three = { .type = EXPR_INT, .text = "3" };
|
||||
Expr add_ident = { .type = EXPR_IDENT, .text = "add" };
|
||||
|
||||
Expr sexpr = { .type = EXPR_SEXPR,
|
||||
.sexpr
|
||||
= { .items = (Expr*[]) { &add_ident, &two, &three }, .count = 3 } };
|
||||
|
||||
IrInst* result = ir_lower_expr(&ir, &sexpr);
|
||||
assert(result != NULL);
|
||||
|
||||
CgBlock cg;
|
||||
cg_block_init(&cg);
|
||||
|
||||
ir_block_isel_x86(&cg, &ir);
|
||||
|
||||
// Expect: IMM 2, IMM 3, ADD
|
||||
assert(cg.count == 3);
|
||||
|
||||
// 1. load 2
|
||||
assert(cg.insts[0]->op == CG_IMM64);
|
||||
assert(cg.insts[0]->imm == 2);
|
||||
|
||||
// 2. load 3
|
||||
assert(cg.insts[1]->op == CG_IMM64);
|
||||
assert(cg.insts[1]->imm == 3);
|
||||
|
||||
// 3. add
|
||||
assert(cg.insts[2]->op == CG_ADD8);
|
||||
|
||||
// operands should reference IR vregs (not recomputed)
|
||||
assert(cg.insts[2]->binop.lhs == ir.insts[0]->vreg);
|
||||
assert(cg.insts[2]->binop.rhs == ir.insts[1]->vreg);
|
||||
|
||||
cg_block_free(&cg);
|
||||
ir_block_free(&ir);
|
||||
}
|
||||
|
||||
static void test_ir_block_isel_nested(void)
|
||||
{
|
||||
IrBlock ir;
|
||||
ir_block_init(&ir);
|
||||
|
||||
// IR: 2 + (3 * 4)
|
||||
|
||||
Expr two = { .type = EXPR_INT, .text = "2" };
|
||||
Expr three = { .type = EXPR_INT, .text = "3" };
|
||||
Expr four = { .type = EXPR_INT, .text = "4" };
|
||||
|
||||
Expr add_ident = { .type = EXPR_IDENT, .text = "add" };
|
||||
Expr mul_ident = { .type = EXPR_IDENT, .text = "mul" };
|
||||
|
||||
Expr mul_expr = { .type = EXPR_SEXPR,
|
||||
.sexpr
|
||||
= { .items = (Expr*[]) { &mul_ident, &three, &four }, .count = 3 } };
|
||||
|
||||
Expr add_expr = { .type = EXPR_SEXPR,
|
||||
.sexpr
|
||||
= { .items = (Expr*[]) { &add_ident, &two, &mul_expr }, .count = 3 } };
|
||||
|
||||
IrInst* result = ir_lower_expr(&ir, &add_expr);
|
||||
assert(result != NULL);
|
||||
|
||||
CgBlock cg;
|
||||
cg_block_init(&cg);
|
||||
|
||||
ir_block_isel_x86(&cg, &ir);
|
||||
|
||||
// Expect IR produces:
|
||||
// int 2, int 3, int 4, mul, add => 5 CG instructions
|
||||
|
||||
assert(cg.count == 5);
|
||||
|
||||
// 0: 2
|
||||
assert(cg.insts[0]->op == CG_IMM64);
|
||||
assert(cg.insts[0]->imm == 2);
|
||||
|
||||
// 1: 3
|
||||
assert(cg.insts[1]->op == CG_IMM64);
|
||||
assert(cg.insts[1]->imm == 3);
|
||||
|
||||
// 2: 4
|
||||
assert(cg.insts[2]->op == CG_IMM64);
|
||||
assert(cg.insts[2]->imm == 4);
|
||||
|
||||
// 3: mul
|
||||
assert(cg.insts[3]->op == CG_MUL8);
|
||||
|
||||
// 4: add
|
||||
assert(cg.insts[4]->op == CG_ADD8);
|
||||
|
||||
// MUL operands: 3 * 4
|
||||
assert(cg.insts[3]->binop.lhs == ir.insts[1]->vreg);
|
||||
assert(cg.insts[3]->binop.rhs == ir.insts[2]->vreg);
|
||||
|
||||
// ADD operands: 2 + (3*4)
|
||||
assert(cg.insts[4]->binop.lhs == ir.insts[0]->vreg);
|
||||
assert(cg.insts[4]->binop.rhs == ir.insts[3]->vreg);
|
||||
|
||||
cg_block_free(&cg);
|
||||
ir_block_free(&ir);
|
||||
}
|
||||
|
||||
static void test_cg_block_regalloc_add(void)
|
||||
{
|
||||
// Build IR: 2 + 3
|
||||
IrBlock ir;
|
||||
ir_block_init(&ir);
|
||||
|
||||
Expr two = { .type = EXPR_INT, .text = "2" };
|
||||
Expr three = { .type = EXPR_INT, .text = "3" };
|
||||
Expr add_ident = { .type = EXPR_IDENT, .text = "add" };
|
||||
|
||||
Expr sexpr = { .type = EXPR_SEXPR,
|
||||
.sexpr
|
||||
= { .items = (Expr*[]) { &add_ident, &two, &three }, .count = 3 } };
|
||||
|
||||
IrInst* result = ir_lower_expr(&ir, &sexpr);
|
||||
assert(result != NULL);
|
||||
|
||||
CgBlock cg;
|
||||
cg_block_init(&cg);
|
||||
|
||||
ir_block_isel_x86(&cg, &ir);
|
||||
|
||||
cg_block_regalloc_x86(&cg);
|
||||
|
||||
// After RA: ADD must use physical registers, not vregs
|
||||
CgInst* add = cg.insts[2];
|
||||
|
||||
assert(add->op == CG_ADD8);
|
||||
|
||||
PhysReg lhs = add->binop.lhs;
|
||||
PhysReg rhs = add->binop.rhs;
|
||||
PhysReg dst = add->dst;
|
||||
|
||||
// sanity: registers must be in valid range
|
||||
assert(lhs < REG_COUNT);
|
||||
assert(rhs < REG_COUNT);
|
||||
assert(dst < REG_COUNT);
|
||||
|
||||
// lhs and rhs should not equal invalid sentinel values
|
||||
assert(lhs != (PhysReg)-1);
|
||||
assert(rhs != (PhysReg)-1);
|
||||
|
||||
cg_block_free(&cg);
|
||||
ir_block_free(&ir);
|
||||
}
|
||||
|
||||
static void test_cg_block_regalloc_nested(void)
|
||||
{
|
||||
// IR: 2 + (3 * 4)
|
||||
IrBlock ir;
|
||||
ir_block_init(&ir);
|
||||
|
||||
Expr two = { .type = EXPR_INT, .text = "2" };
|
||||
Expr three = { .type = EXPR_INT, .text = "3" };
|
||||
Expr four = { .type = EXPR_INT, .text = "4" };
|
||||
|
||||
Expr add_ident = { .type = EXPR_IDENT, .text = "add" };
|
||||
Expr mul_ident = { .type = EXPR_IDENT, .text = "mul" };
|
||||
|
||||
Expr mul_expr = { .type = EXPR_SEXPR,
|
||||
.sexpr
|
||||
= { .items = (Expr*[]) { &mul_ident, &three, &four }, .count = 3 } };
|
||||
|
||||
Expr add_expr = { .type = EXPR_SEXPR,
|
||||
.sexpr
|
||||
= { .items = (Expr*[]) { &add_ident, &two, &mul_expr }, .count = 3 } };
|
||||
|
||||
IrInst* result = ir_lower_expr(&ir, &add_expr);
|
||||
assert(result != NULL);
|
||||
|
||||
CgBlock cg;
|
||||
cg_block_init(&cg);
|
||||
|
||||
ir_block_isel_x86(&cg, &ir);
|
||||
|
||||
cg_block_regalloc_x86(&cg);
|
||||
|
||||
// ADD instruction is last
|
||||
CgInst* add = cg.insts[4];
|
||||
assert(add->op == CG_ADD8);
|
||||
|
||||
// MUL instruction
|
||||
CgInst* mul = cg.insts[3];
|
||||
assert(mul->op == CG_MUL8);
|
||||
|
||||
// Check register validity
|
||||
assert(add->binop.lhs < REG_COUNT);
|
||||
assert(add->binop.rhs < REG_COUNT);
|
||||
assert(add->dst < REG_COUNT);
|
||||
|
||||
assert(mul->binop.lhs < REG_COUNT);
|
||||
assert(mul->binop.rhs < REG_COUNT);
|
||||
assert(mul->dst < REG_COUNT);
|
||||
|
||||
// Critical correctness check:
|
||||
// MUL result should be used by ADD
|
||||
assert(add->binop.rhs == mul->dst);
|
||||
|
||||
cg_block_free(&cg);
|
||||
ir_block_free(&ir);
|
||||
}
|
||||
|
||||
void test_codegen_x86(void)
|
||||
{
|
||||
test_ir_block_isel_add();
|
||||
test_ir_block_isel_nested();
|
||||
test_cg_block_regalloc_add();
|
||||
test_cg_block_regalloc_nested();
|
||||
}
|
||||
73
codegen_x86.h
Normal file
73
codegen_x86.h
Normal file
@ -0,0 +1,73 @@
|
||||
#ifndef CODEGEN_X86_H
|
||||
#define CODEGEN_X86_H
|
||||
|
||||
#include "arena.h"
|
||||
#include "ir.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
CG_MOV,
|
||||
CG_ADD8,
|
||||
CG_SUB8,
|
||||
CG_MUL8,
|
||||
CG_IMM64
|
||||
} CgOpCode;
|
||||
|
||||
typedef enum {
|
||||
RAX = 0,
|
||||
RDX,
|
||||
RCX,
|
||||
RBX,
|
||||
RSI,
|
||||
RDI,
|
||||
R8,
|
||||
R9,
|
||||
REG_COUNT
|
||||
} PhysReg;
|
||||
|
||||
typedef struct {
|
||||
PhysReg reg;
|
||||
int assigned;
|
||||
} RegMap;
|
||||
|
||||
typedef struct CgInst {
|
||||
CgOpCode op;
|
||||
|
||||
VReg dst;
|
||||
|
||||
union {
|
||||
struct {
|
||||
VReg lhs;
|
||||
VReg rhs;
|
||||
} binop;
|
||||
|
||||
uint64_t imm;
|
||||
};
|
||||
} CgInst;
|
||||
|
||||
typedef struct {
|
||||
CgInst** insts;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
|
||||
VReg next_vreg;
|
||||
RegMap* vreg_map;
|
||||
size_t vreg_map_size;
|
||||
|
||||
Arena arena;
|
||||
VReg result_vreg;
|
||||
} CgBlock;
|
||||
|
||||
|
||||
void cg_block_init(CgBlock* b);
|
||||
void cg_block_free(CgBlock* b);
|
||||
void cg_block_print_vreg(const CgBlock* block);
|
||||
void cg_block_print_phys(const CgBlock* block);
|
||||
|
||||
void ir_block_isel_x86(CgBlock* cg, const IrBlock* ir);
|
||||
void cg_block_regalloc_x86(CgBlock* block);
|
||||
|
||||
void test_codegen_x86(void);
|
||||
|
||||
#endif
|
||||
6
compile_flags.txt
Normal file
6
compile_flags.txt
Normal file
@ -0,0 +1,6 @@
|
||||
-xc
|
||||
-std=c23
|
||||
-Wall
|
||||
-Wextra
|
||||
-pedantic-errors
|
||||
-Wno-empty-translation-unit
|
||||
328
ir.c
Normal file
328
ir.c
Normal file
@ -0,0 +1,328 @@
|
||||
#include "ir.h"
|
||||
#include "arena.h"
|
||||
#include "parse.h"
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void ir_block_init(IrBlock* block)
|
||||
{
|
||||
arena_init(&block->arena);
|
||||
|
||||
block->next_vreg = 0;
|
||||
block->insts = NULL;
|
||||
block->count = 0;
|
||||
block->capacity = 0;
|
||||
}
|
||||
|
||||
void ir_block_free(IrBlock* block)
|
||||
{
|
||||
if (!block)
|
||||
return;
|
||||
|
||||
arena_free(&block->arena);
|
||||
free(block->insts);
|
||||
}
|
||||
|
||||
static int ir_inst_index(IrBlock* block, IrInst* inst)
|
||||
{
|
||||
for (size_t i = 0; i < block->count; i++) {
|
||||
if (block->insts[i] == inst)
|
||||
return (int)i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ir_block_print(IrBlock* block)
|
||||
{
|
||||
if (!block)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < block->count; i++) {
|
||||
IrInst* inst = block->insts[i];
|
||||
|
||||
printf("%%%zu = ", i);
|
||||
|
||||
switch (inst->op) {
|
||||
case OP_INT:
|
||||
printf("Int %llu\n", (unsigned long long)inst->value);
|
||||
break;
|
||||
|
||||
case OP_ADD:
|
||||
case OP_SUB:
|
||||
case OP_MUL: {
|
||||
const char* op_str = inst->op == OP_ADD ? "Add"
|
||||
: inst->op == OP_SUB ? "Sub"
|
||||
: "Mul";
|
||||
|
||||
printf("%s ", op_str);
|
||||
|
||||
for (size_t j = 0; j < inst->operand_count; j++) {
|
||||
int idx = ir_inst_index(block, inst->operands[j]);
|
||||
|
||||
if (idx < 0) {
|
||||
printf("<?>");
|
||||
} else {
|
||||
printf("%%%d", idx);
|
||||
}
|
||||
|
||||
if (j + 1 < inst->operand_count)
|
||||
printf(", ");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
printf("UnknownOp\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ir_block_emit(IrBlock* block, IrInst* inst)
|
||||
{
|
||||
if (!block || !inst)
|
||||
return;
|
||||
|
||||
if (block->count == block->capacity) {
|
||||
size_t new_cap = block->capacity ? block->capacity * 2 : 8;
|
||||
|
||||
IrInst** new_buf = realloc(block->insts, new_cap * sizeof(IrInst*));
|
||||
if (!new_buf)
|
||||
return;
|
||||
|
||||
block->insts = new_buf;
|
||||
block->capacity = new_cap;
|
||||
}
|
||||
|
||||
block->insts[block->count++] = inst;
|
||||
}
|
||||
|
||||
static IrInst* ir_alloc_inst(IrBlock* block)
|
||||
{
|
||||
IrInst* inst = arena_alloc(&block->arena, sizeof(IrInst));
|
||||
if (!inst)
|
||||
return NULL;
|
||||
|
||||
memset(inst, 0, sizeof(IrInst));
|
||||
return inst;
|
||||
}
|
||||
|
||||
static IrInst* ir_new_int(IrBlock* block, uint64_t value)
|
||||
{
|
||||
IrInst* inst = ir_alloc_inst(block);
|
||||
if (!inst)
|
||||
return NULL;
|
||||
|
||||
inst->op = OP_INT;
|
||||
inst->value = value;
|
||||
inst->vreg = block->next_vreg++;
|
||||
|
||||
return ir_block_emit(block, inst), inst;
|
||||
}
|
||||
|
||||
static IrInst* ir_new_binop(IrBlock* block, OpCode op, IrInst* a, IrInst* b)
|
||||
{
|
||||
IrInst* inst = ir_alloc_inst(block);
|
||||
if (!inst)
|
||||
return NULL;
|
||||
|
||||
inst->op = op;
|
||||
inst->operand_count = 2;
|
||||
inst->operands[0] = a;
|
||||
inst->operands[1] = b;
|
||||
inst->vreg = block->next_vreg++;
|
||||
|
||||
return ir_block_emit(block, inst), inst;
|
||||
}
|
||||
|
||||
static OpCode op_from_ident_strict(const char* s)
|
||||
{
|
||||
if (strcmp(s, "add") == 0)
|
||||
return OP_ADD;
|
||||
if (strcmp(s, "sub") == 0)
|
||||
return OP_SUB;
|
||||
if (strcmp(s, "mul") == 0)
|
||||
return OP_MUL;
|
||||
return (OpCode)-1;
|
||||
}
|
||||
|
||||
static bool is_int_literal(const char* s, uint64_t* out)
|
||||
{
|
||||
if (!s || !*s)
|
||||
return false;
|
||||
char* end = NULL;
|
||||
unsigned long long v = strtoull(s, &end, 10);
|
||||
if (*end != '\0')
|
||||
return false;
|
||||
*out = (uint64_t)v;
|
||||
return true;
|
||||
}
|
||||
|
||||
IrInst* ir_lower_expr(IrBlock* block, const Expr* expr)
|
||||
{
|
||||
if (!expr || !block)
|
||||
return NULL;
|
||||
|
||||
switch (expr->type) {
|
||||
|
||||
case EXPR_INT: {
|
||||
uint64_t value;
|
||||
if (!is_int_literal(expr->text, &value)) {
|
||||
return NULL; // strict: invalid integer literal
|
||||
}
|
||||
return ir_new_int(block, value);
|
||||
}
|
||||
|
||||
case EXPR_IDENT:
|
||||
// identifiers not supported in this IR
|
||||
return NULL;
|
||||
|
||||
case EXPR_SEXPR: {
|
||||
size_t n = expr->sexpr.count;
|
||||
|
||||
if (n == 0) {
|
||||
return NULL; // malformed: empty s-expression
|
||||
}
|
||||
|
||||
const Expr* head = expr->sexpr.items[0];
|
||||
if (!head || head->type != EXPR_IDENT) {
|
||||
return NULL; // strict: first element must be operator
|
||||
}
|
||||
|
||||
OpCode op = op_from_ident_strict(head->text);
|
||||
if (op == (OpCode)-1) {
|
||||
return NULL; // unknown operator
|
||||
}
|
||||
|
||||
size_t argc = n - 1;
|
||||
|
||||
// STRICT ARITY RULES (you can adjust these)
|
||||
if (argc < 2) {
|
||||
return NULL; // e.g. (add x) is invalid
|
||||
}
|
||||
|
||||
if (argc > IR_MAX_ARITY) {
|
||||
return NULL; // prevent overflow
|
||||
}
|
||||
|
||||
// Lower first operand
|
||||
IrInst* first = ir_lower_expr(block, expr->sexpr.items[1]);
|
||||
if (!first)
|
||||
return NULL;
|
||||
|
||||
IrInst* acc = first;
|
||||
|
||||
// Left-associative lowering:
|
||||
// (add a b c d) => (((a + b) + c) + d)
|
||||
for (size_t i = 2; i < n; i++) {
|
||||
IrInst* rhs = ir_lower_expr(block, expr->sexpr.items[i]);
|
||||
if (!rhs)
|
||||
return NULL;
|
||||
|
||||
IrInst* tmp = ir_new_binop(block, op, acc, rhs);
|
||||
if (!tmp)
|
||||
return NULL;
|
||||
|
||||
acc = tmp;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void test_ir_lower_simple_add(void)
|
||||
{
|
||||
IrBlock block;
|
||||
ir_block_init(&block);
|
||||
|
||||
// Build AST: (add 2 3)
|
||||
Expr two = { .type = EXPR_INT, .text = "2" };
|
||||
Expr three = { .type = EXPR_INT, .text = "3" };
|
||||
|
||||
Expr add_ident = { .type = EXPR_IDENT, .text = "add" };
|
||||
|
||||
Expr sexpr = { .type = EXPR_SEXPR,
|
||||
.sexpr
|
||||
= { .items = (Expr*[]) { &add_ident, &two, &three }, .count = 3 } };
|
||||
|
||||
IrInst* result = ir_lower_expr(&block, &sexpr);
|
||||
assert(result != NULL);
|
||||
|
||||
// Check instruction count
|
||||
assert(block.count == 3);
|
||||
|
||||
// Int 2
|
||||
assert(block.insts[0]->op == OP_INT);
|
||||
assert(block.insts[0]->value == 2);
|
||||
|
||||
// Int 3
|
||||
assert(block.insts[1]->op == OP_INT);
|
||||
assert(block.insts[1]->value == 3);
|
||||
|
||||
// Add
|
||||
assert(block.insts[2]->op == OP_ADD);
|
||||
|
||||
// Operand wiring
|
||||
assert(block.insts[2]->operands[0] == block.insts[0]);
|
||||
assert(block.insts[2]->operands[1] == block.insts[1]);
|
||||
|
||||
ir_block_free(&block);
|
||||
}
|
||||
|
||||
static void test_ir_lower_nested_expr(void)
|
||||
{
|
||||
IrBlock block;
|
||||
ir_block_init(&block);
|
||||
|
||||
Expr two = { .type = EXPR_INT, .text = "2" };
|
||||
Expr three = { .type = EXPR_INT, .text = "3" };
|
||||
Expr four = { .type = EXPR_INT, .text = "4" };
|
||||
|
||||
Expr mul_ident = { .type = EXPR_IDENT, .text = "mul" };
|
||||
Expr add_ident = { .type = EXPR_IDENT, .text = "add" };
|
||||
|
||||
Expr mul_expr = { .type = EXPR_SEXPR,
|
||||
.sexpr
|
||||
= { .items = (Expr*[]) { &mul_ident, &three, &four }, .count = 3 } };
|
||||
|
||||
Expr add_expr = { .type = EXPR_SEXPR,
|
||||
.sexpr
|
||||
= { .items = (Expr*[]) { &add_ident, &two, &mul_expr }, .count = 3 } };
|
||||
|
||||
IrInst* result = ir_lower_expr(&block, &add_expr);
|
||||
assert(result != NULL);
|
||||
|
||||
assert(block.count == 5);
|
||||
|
||||
assert(block.insts[0]->op == OP_INT && block.insts[0]->value == 2);
|
||||
assert(block.insts[1]->op == OP_INT && block.insts[1]->value == 3);
|
||||
assert(block.insts[2]->op == OP_INT && block.insts[2]->value == 4);
|
||||
|
||||
assert(block.insts[3]->op == OP_MUL);
|
||||
assert(block.insts[4]->op == OP_ADD);
|
||||
|
||||
// Verify MUL operands
|
||||
assert(block.insts[3]->operands[0] == block.insts[1]);
|
||||
assert(block.insts[3]->operands[1] == block.insts[2]);
|
||||
|
||||
// Verify ADD operands
|
||||
assert(block.insts[4]->operands[0] == block.insts[0]);
|
||||
assert(block.insts[4]->operands[1] == block.insts[3]);
|
||||
|
||||
ir_block_free(&block);
|
||||
}
|
||||
|
||||
void test_ast_lower(void)
|
||||
{
|
||||
test_ir_lower_simple_add();
|
||||
test_ir_lower_nested_expr();
|
||||
}
|
||||
54
ir.h
Normal file
54
ir.h
Normal file
@ -0,0 +1,54 @@
|
||||
#ifndef IR_H
|
||||
#define IR_H
|
||||
|
||||
#include "arena.h"
|
||||
#include "parse.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef uint32_t VReg;
|
||||
|
||||
#define IR_MAX_ARITY 8 // adjust as needed
|
||||
|
||||
typedef enum {
|
||||
OP_INT,
|
||||
OP_ADD,
|
||||
OP_SUB,
|
||||
OP_MUL
|
||||
} OpCode;
|
||||
|
||||
typedef struct IrInst IrInst;
|
||||
|
||||
struct IrInst {
|
||||
OpCode op;
|
||||
VReg vreg;
|
||||
|
||||
union {
|
||||
uint64_t value; // OP_INT
|
||||
|
||||
struct {
|
||||
size_t operand_count;
|
||||
IrInst* operands[IR_MAX_ARITY]; // inline storage
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
Arena arena;
|
||||
VReg next_vreg;
|
||||
|
||||
IrInst** insts;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} IrBlock;
|
||||
|
||||
void ir_block_init(IrBlock* block);
|
||||
void ir_block_free(IrBlock* block);
|
||||
void ir_block_print(IrBlock* block);
|
||||
|
||||
IrInst* ir_lower_expr(IrBlock* block, const Expr* expr);
|
||||
|
||||
void test_ast_lower(void);
|
||||
|
||||
#endif
|
||||
188
jit_x86.c
Normal file
188
jit_x86.c
Normal file
@ -0,0 +1,188 @@
|
||||
#include "jit_x86.h"
|
||||
#include "codegen_x86.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static uint8_t reg_enc(PhysReg r)
|
||||
{
|
||||
switch (r) {
|
||||
case RAX:
|
||||
return 0;
|
||||
case RCX:
|
||||
return 1;
|
||||
case RDX:
|
||||
return 2;
|
||||
case RBX:
|
||||
return 3;
|
||||
case RSI:
|
||||
return 6;
|
||||
case RDI:
|
||||
return 7;
|
||||
case R8:
|
||||
return 8;
|
||||
case R9:
|
||||
return 9;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint8_t* buf;
|
||||
size_t cap;
|
||||
size_t len;
|
||||
} AsmBuf;
|
||||
|
||||
static void emit8(AsmBuf* a, uint8_t v)
|
||||
{
|
||||
if (a->len >= a->cap) {
|
||||
fprintf(stderr, "JIT buffer overflow\n");
|
||||
abort();
|
||||
}
|
||||
a->buf[a->len++] = v;
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
static void emit32(AsmBuf* a, uint32_t v)
|
||||
{
|
||||
if (a->len >= a->cap) {
|
||||
fprintf(stderr, "JIT buffer overflow\n");
|
||||
abort();
|
||||
}
|
||||
memcpy(&a->buf[a->len], &v, 4);
|
||||
a->len += 4;
|
||||
}
|
||||
|
||||
static void emit64(AsmBuf* a, uint64_t v)
|
||||
{
|
||||
if (a->len >= a->cap) {
|
||||
fprintf(stderr, "JIT buffer overflow\n");
|
||||
abort();
|
||||
}
|
||||
memcpy(&a->buf[a->len], &v, 8);
|
||||
a->len += 8;
|
||||
}
|
||||
|
||||
static void emit_mov_imm64(AsmBuf* a, PhysReg dst, uint64_t imm)
|
||||
{
|
||||
uint8_t r = reg_enc(dst);
|
||||
|
||||
// mov r64, imm64 = 48 B8+rd imm64
|
||||
emit8(a, 0x48);
|
||||
emit8(a, 0xB8 + r);
|
||||
emit64(a, imm);
|
||||
}
|
||||
|
||||
static uint8_t rex_enc(uint8_t dst, uint8_t src)
|
||||
{
|
||||
return 0x40 | ((dst & 8) ? 0x01 : 0) | // B
|
||||
((src & 8) ? 0x04 : 0); // R
|
||||
}
|
||||
|
||||
static void emit_alu_rr(
|
||||
AsmBuf* a, uint8_t rex, uint8_t op, PhysReg dst, PhysReg src)
|
||||
{
|
||||
(void)rex;
|
||||
|
||||
uint8_t d = reg_enc(dst);
|
||||
uint8_t s = reg_enc(src);
|
||||
|
||||
emit8(a, rex_enc(d, s));
|
||||
emit8(a, op);
|
||||
|
||||
uint8_t modrm = 0xC0 | ((s & 7) << 3) | (d & 7);
|
||||
emit8(a, modrm);
|
||||
}
|
||||
|
||||
static void emit_add(AsmBuf* a, PhysReg d, PhysReg s)
|
||||
{
|
||||
emit_alu_rr(a, 0x48, 0x01, d, s);
|
||||
}
|
||||
|
||||
static void emit_sub(AsmBuf* a, PhysReg d, PhysReg s)
|
||||
{
|
||||
emit_alu_rr(a, 0x48, 0x29, d, s);
|
||||
}
|
||||
|
||||
static void emit_mul(AsmBuf* a, PhysReg d, PhysReg s)
|
||||
{
|
||||
emit8(a, 0x48);
|
||||
emit8(a, 0x0F);
|
||||
emit8(a, 0xAF);
|
||||
emit8(a, 0xC0 | (reg_enc(s) << 3) | reg_enc(d));
|
||||
}
|
||||
|
||||
static void emit_ret(AsmBuf* a)
|
||||
{
|
||||
emit8(a, 0xC3);
|
||||
}
|
||||
|
||||
static size_t align_page(size_t n)
|
||||
{
|
||||
size_t page = sysconf(_SC_PAGESIZE);
|
||||
return (n + page - 1) & ~(page - 1);
|
||||
}
|
||||
|
||||
JitFn cg_block_emit_x86_machine_code(CgBlock* block)
|
||||
{
|
||||
AsmBuf a = { 0 };
|
||||
|
||||
a.cap = align_page(1024);
|
||||
a.len = 0;
|
||||
a.buf = mmap(NULL,
|
||||
a.cap,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
-1,
|
||||
0);
|
||||
|
||||
if (a.buf == MAP_FAILED) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < block->count; i++) {
|
||||
CgInst* inst = block->insts[i];
|
||||
|
||||
switch (inst->op) {
|
||||
|
||||
case CG_IMM64:
|
||||
emit_mov_imm64(&a, inst->dst, inst->imm);
|
||||
break;
|
||||
|
||||
case CG_ADD8:
|
||||
emit_add(&a, inst->dst, inst->binop.rhs);
|
||||
break;
|
||||
|
||||
case CG_SUB8:
|
||||
emit_sub(&a, inst->dst, inst->binop.rhs);
|
||||
break;
|
||||
|
||||
case CG_MUL8:
|
||||
emit_mul(&a, inst->dst, inst->binop.rhs);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ensure result is in rax (ABI return register)
|
||||
emit_ret(&a);
|
||||
|
||||
// make executable
|
||||
mprotect(a.buf, align_page(a.len), PROT_READ | PROT_EXEC);
|
||||
|
||||
for (size_t i = 0; i < a.len; i++) {
|
||||
printf("%02x ", a.buf[i]);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
return (JitFn)a.buf;
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
11
jit_x86.h
Normal file
11
jit_x86.h
Normal file
@ -0,0 +1,11 @@
|
||||
#ifndef JIT_X86_H
|
||||
#define JIT_X86_H
|
||||
|
||||
#include "codegen_x86.h"
|
||||
#include <stdint.h>
|
||||
|
||||
typedef uint64_t (*JitFn)(void);
|
||||
|
||||
JitFn cg_block_emit_x86_machine_code(CgBlock* block);
|
||||
|
||||
#endif
|
||||
105
main.c
Normal file
105
main.c
Normal file
@ -0,0 +1,105 @@
|
||||
#include "codegen_x86.h"
|
||||
#include "ir.h"
|
||||
#include "jit_x86.h"
|
||||
#include "parse.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static char* read_file(const char* filename)
|
||||
{
|
||||
FILE* file = fopen(filename, "rb");
|
||||
|
||||
if (!file) {
|
||||
perror("fopen");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(file, 0, SEEK_END) != 0) {
|
||||
perror("fseek");
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
long size = ftell(file);
|
||||
|
||||
if (size < 0) {
|
||||
perror("ftell");
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rewind(file);
|
||||
|
||||
char* buffer = malloc((size_t)size + 1);
|
||||
|
||||
if (!buffer) {
|
||||
fprintf(stderr, "Out of memory\n");
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t bytes_read = fread(buffer, 1, (size_t)size, file);
|
||||
|
||||
if (bytes_read != (size_t)size) {
|
||||
perror("fread");
|
||||
free(buffer);
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buffer[size] = '\0';
|
||||
|
||||
fclose(file);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc > 1 && strcmp(argv[1], "--test") == 0) {
|
||||
test_parse();
|
||||
test_ast_lower();
|
||||
test_codegen_x86();
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(argc > 1);
|
||||
char* text = read_file(argv[1]);
|
||||
printf("--- text ---\n");
|
||||
puts(text);
|
||||
|
||||
Expr* expr = parse(text);
|
||||
free(text);
|
||||
printf("--- ast ---\n");
|
||||
expr_print(expr);
|
||||
|
||||
IrBlock ir_block;
|
||||
ir_block_init(&ir_block);
|
||||
|
||||
ir_lower_expr(&ir_block, expr);
|
||||
expr_free(expr);
|
||||
printf("\n--- ir ---\n");
|
||||
ir_block_print(&ir_block);
|
||||
|
||||
CgBlock cg_block;
|
||||
cg_block_init(&cg_block);
|
||||
|
||||
ir_block_isel_x86(&cg_block, &ir_block);
|
||||
ir_block_free(&ir_block);
|
||||
printf("--- isel ---\n");
|
||||
cg_block_print_vreg(&cg_block);
|
||||
|
||||
printf("--- regalloc ---\n");
|
||||
cg_block_regalloc_x86(&cg_block);
|
||||
cg_block_print_phys(&cg_block);
|
||||
|
||||
JitFn fn = cg_block_emit_x86_machine_code(&cg_block);
|
||||
cg_block_free(&cg_block);
|
||||
printf("--- result ---\n");
|
||||
uint64_t result = fn();
|
||||
printf("%lu\n", result);
|
||||
|
||||
return 0;
|
||||
}
|
||||
354
parse.c
Normal file
354
parse.c
Normal file
@ -0,0 +1,354 @@
|
||||
// parser.c
|
||||
#include "parse.h"
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef enum {
|
||||
TOKEN_LPAREN,
|
||||
TOKEN_RPAREN,
|
||||
TOKEN_IDENT,
|
||||
TOKEN_INT,
|
||||
TOKEN_EOF,
|
||||
} TokenType;
|
||||
|
||||
typedef struct {
|
||||
TokenType type;
|
||||
char* text;
|
||||
} Token;
|
||||
|
||||
typedef struct {
|
||||
const char* input;
|
||||
size_t pos;
|
||||
} Lexer;
|
||||
|
||||
typedef struct {
|
||||
Lexer lexer;
|
||||
Token current;
|
||||
} Parser;
|
||||
|
||||
/* =========================
|
||||
Lexer
|
||||
========================= */
|
||||
|
||||
static char current_char(Lexer* lexer)
|
||||
{
|
||||
return lexer->input[lexer->pos];
|
||||
}
|
||||
|
||||
static void advance(Lexer* lexer)
|
||||
{
|
||||
if (current_char(lexer) != '\0') {
|
||||
lexer->pos++;
|
||||
}
|
||||
}
|
||||
|
||||
static void skip_whitespace(Lexer* lexer)
|
||||
{
|
||||
while (isspace(current_char(lexer))) {
|
||||
advance(lexer);
|
||||
}
|
||||
}
|
||||
|
||||
static Token make_ident(Lexer* lexer)
|
||||
{
|
||||
size_t start = lexer->pos;
|
||||
|
||||
while (isalnum(current_char(lexer)) || current_char(lexer) == '_') {
|
||||
advance(lexer);
|
||||
}
|
||||
|
||||
return (Token) {
|
||||
.type = TOKEN_IDENT,
|
||||
.text = strndup(lexer->input + start, lexer->pos - start),
|
||||
};
|
||||
}
|
||||
|
||||
static Token make_int(Lexer* lexer)
|
||||
{
|
||||
size_t start = lexer->pos;
|
||||
|
||||
while (isdigit(current_char(lexer))) {
|
||||
advance(lexer);
|
||||
}
|
||||
|
||||
return (Token) {
|
||||
.type = TOKEN_INT,
|
||||
.text = strndup(lexer->input + start, lexer->pos - start),
|
||||
};
|
||||
}
|
||||
|
||||
static Token next_token(Lexer* lexer)
|
||||
{
|
||||
skip_whitespace(lexer);
|
||||
|
||||
char c = current_char(lexer);
|
||||
|
||||
switch (c) {
|
||||
case '\0':
|
||||
return (Token) {
|
||||
.type = TOKEN_EOF,
|
||||
.text = NULL,
|
||||
};
|
||||
|
||||
case '(':
|
||||
advance(lexer);
|
||||
|
||||
return (Token) {
|
||||
.type = TOKEN_LPAREN,
|
||||
.text = NULL,
|
||||
};
|
||||
|
||||
case ')':
|
||||
advance(lexer);
|
||||
|
||||
return (Token) {
|
||||
.type = TOKEN_RPAREN,
|
||||
.text = NULL,
|
||||
};
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (isalpha(c) || c == '_') {
|
||||
return make_ident(lexer);
|
||||
}
|
||||
|
||||
if (isdigit(c)) {
|
||||
return make_int(lexer);
|
||||
}
|
||||
|
||||
fprintf(stderr, "Unexpected character: '%c'\n", c);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Parser
|
||||
========================= */
|
||||
|
||||
static void parser_advance(Parser* parser)
|
||||
{
|
||||
free(parser->current.text);
|
||||
parser->current = next_token(&parser->lexer);
|
||||
}
|
||||
|
||||
static void parser_expect(Parser* parser, TokenType expected)
|
||||
{
|
||||
if (parser->current.type != expected) {
|
||||
fprintf(stderr, "Unexpected token\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
static Expr* make_atom_expr(ExprType type, char* text)
|
||||
{
|
||||
Expr* expr = malloc(sizeof(Expr));
|
||||
|
||||
expr->type = type;
|
||||
expr->text = text;
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
static Expr* make_sexpr(void)
|
||||
{
|
||||
Expr* expr = malloc(sizeof(Expr));
|
||||
|
||||
expr->type = EXPR_SEXPR;
|
||||
expr->sexpr.items = NULL;
|
||||
expr->sexpr.count = 0;
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
static void sexpr_push(Expr* sexpr, Expr* item)
|
||||
{
|
||||
sexpr->sexpr.items
|
||||
= realloc(sexpr->sexpr.items, sizeof(Expr*) * (sexpr->sexpr.count + 1));
|
||||
|
||||
sexpr->sexpr.items[sexpr->sexpr.count++] = item;
|
||||
}
|
||||
|
||||
static Expr* parse_expr(Parser* parser);
|
||||
|
||||
static Expr* parse_list(Parser* parser)
|
||||
{
|
||||
parser_expect(parser, TOKEN_LPAREN);
|
||||
parser_advance(parser);
|
||||
|
||||
Expr* sexpr = make_sexpr();
|
||||
|
||||
while (parser->current.type != TOKEN_RPAREN) {
|
||||
Expr* expr = parse_expr(parser);
|
||||
sexpr_push(sexpr, expr);
|
||||
}
|
||||
|
||||
parser_expect(parser, TOKEN_RPAREN);
|
||||
parser_advance(parser);
|
||||
|
||||
return sexpr;
|
||||
}
|
||||
|
||||
static Expr* parse_expr(Parser* parser)
|
||||
{
|
||||
switch (parser->current.type) {
|
||||
case TOKEN_IDENT: {
|
||||
char* text = strdup(parser->current.text);
|
||||
|
||||
parser_advance(parser);
|
||||
|
||||
return make_atom_expr(EXPR_IDENT, text);
|
||||
}
|
||||
|
||||
case TOKEN_INT: {
|
||||
char* text = strdup(parser->current.text);
|
||||
|
||||
parser_advance(parser);
|
||||
|
||||
return make_atom_expr(EXPR_INT, text);
|
||||
}
|
||||
|
||||
case TOKEN_LPAREN:
|
||||
return parse_list(parser);
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unexpected token in expression\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Public API
|
||||
========================= */
|
||||
|
||||
Expr* parse(const char* source)
|
||||
{
|
||||
Parser parser = {
|
||||
.lexer = {
|
||||
.input = source,
|
||||
.pos = 0,
|
||||
},
|
||||
.current = {0},
|
||||
};
|
||||
|
||||
parser.current = next_token(&parser.lexer);
|
||||
|
||||
Expr* expr = parse_expr(&parser);
|
||||
|
||||
if (parser.current.type != TOKEN_EOF) {
|
||||
fprintf(stderr, "Expected EOF\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
free(parser.current.text);
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Debug Printing
|
||||
========================= */
|
||||
|
||||
static void print_sexpr(Expr* expr)
|
||||
{
|
||||
printf("SExpr [ ");
|
||||
|
||||
for (size_t i = 0; i < expr->sexpr.count; i++) {
|
||||
expr_print(expr->sexpr.items[i]);
|
||||
|
||||
if (i + 1 < expr->sexpr.count) {
|
||||
printf(", ");
|
||||
}
|
||||
}
|
||||
|
||||
printf(" ]");
|
||||
}
|
||||
|
||||
void expr_print(Expr* expr)
|
||||
{
|
||||
switch (expr->type) {
|
||||
case EXPR_IDENT:
|
||||
printf("Ident(\"%s\")", expr->text);
|
||||
break;
|
||||
|
||||
case EXPR_INT:
|
||||
printf("Int(%s)", expr->text);
|
||||
break;
|
||||
|
||||
case EXPR_SEXPR:
|
||||
print_sexpr(expr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Memory Cleanup
|
||||
========================= */
|
||||
|
||||
void expr_free(Expr* expr)
|
||||
{
|
||||
switch (expr->type) {
|
||||
case EXPR_IDENT:
|
||||
case EXPR_INT:
|
||||
free(expr->text);
|
||||
break;
|
||||
|
||||
case EXPR_SEXPR:
|
||||
for (size_t i = 0; i < expr->sexpr.count; i++) {
|
||||
expr_free(expr->sexpr.items[i]);
|
||||
}
|
||||
|
||||
free(expr->sexpr.items);
|
||||
break;
|
||||
}
|
||||
|
||||
free(expr);
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Unit Tests
|
||||
========================= */
|
||||
|
||||
static void test_simple_list(void)
|
||||
{
|
||||
Expr* expr = parse("(add 2)");
|
||||
|
||||
assert(expr->type == EXPR_SEXPR);
|
||||
assert(expr->sexpr.count == 2);
|
||||
|
||||
assert(expr->sexpr.items[0]->type == EXPR_IDENT);
|
||||
assert(strcmp(expr->sexpr.items[0]->text, "add") == 0);
|
||||
|
||||
assert(expr->sexpr.items[1]->type == EXPR_INT);
|
||||
assert(strcmp(expr->sexpr.items[1]->text, "2") == 0);
|
||||
|
||||
expr_free(expr);
|
||||
}
|
||||
|
||||
static void test_nested_list(void)
|
||||
{
|
||||
Expr* expr = parse("(add 2 (mul 3 4))");
|
||||
|
||||
assert(expr->type == EXPR_SEXPR);
|
||||
assert(expr->sexpr.count == 3);
|
||||
|
||||
Expr* nested = expr->sexpr.items[2];
|
||||
|
||||
assert(nested->type == EXPR_SEXPR);
|
||||
assert(nested->sexpr.count == 3);
|
||||
|
||||
assert(strcmp(nested->sexpr.items[0]->text, "mul") == 0);
|
||||
assert(strcmp(nested->sexpr.items[1]->text, "3") == 0);
|
||||
assert(strcmp(nested->sexpr.items[2]->text, "4") == 0);
|
||||
|
||||
expr_free(expr);
|
||||
}
|
||||
|
||||
void test_parse(void)
|
||||
{
|
||||
test_simple_list();
|
||||
test_nested_list();
|
||||
}
|
||||
33
parse.h
Normal file
33
parse.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef PARSE_H
|
||||
#define PARSE_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef enum {
|
||||
EXPR_IDENT,
|
||||
EXPR_INT,
|
||||
EXPR_SEXPR,
|
||||
} ExprType;
|
||||
|
||||
typedef struct Expr Expr;
|
||||
|
||||
struct Expr {
|
||||
ExprType type;
|
||||
|
||||
union {
|
||||
char* text;
|
||||
|
||||
struct {
|
||||
Expr** items;
|
||||
size_t count;
|
||||
} sexpr;
|
||||
};
|
||||
};
|
||||
|
||||
Expr* parse(const char* source);
|
||||
void expr_free(Expr* expr);
|
||||
void expr_print(Expr* expr);
|
||||
|
||||
void test_parse(void);
|
||||
|
||||
#endif
|
||||
Loading…
x
Reference in New Issue
Block a user