From 8b76d35b0a045e0a3277be422513b938ef33dcce Mon Sep 17 00:00:00 2001 From: hachem Date: Wed, 9 Sep 2026 05:14:44 +0200 Subject: feat: fasm backend --- src/ast.h | 2 + src/codegen.c | 1508 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/codegen.h | 8 + src/main.c | 11 +- src/nasm.c | 1351 --------------------------------------------------- src/nasm.h | 7 - src/parser.c | 10 + 7 files changed, 1535 insertions(+), 1362 deletions(-) create mode 100644 src/codegen.c create mode 100644 src/codegen.h delete mode 100644 src/nasm.c delete mode 100644 src/nasm.h (limited to 'src') diff --git a/src/ast.h b/src/ast.h index f5eea17..d764c13 100644 --- a/src/ast.h +++ b/src/ast.h @@ -152,6 +152,8 @@ struct IfStatement struct WhileStatement { + bool named; + struct Token name; struct Expr* left; struct Token comparison; struct Expr* right; diff --git a/src/codegen.c b/src/codegen.c new file mode 100644 index 0000000..2eaa8bd --- /dev/null +++ b/src/codegen.c @@ -0,0 +1,1508 @@ +#include +#include +#include +#include +#include +#include + +#include "codegen.h" + +static void emit_const_expr(struct Expr* expr, FILE* out) +{ + switch (expr->kind) + { + case EXPR_PRIMARY: + fprintf(out, "%.*s", (int)expr->primary.token.length, expr->primary.token.start); + break; + case EXPR_UNARY: + fprintf(out, "%.*s", (int)expr->unary.op.length, expr->unary.op.start); + emit_const_expr(expr->unary.operand, out); + break; + case EXPR_BINARY: + emit_const_expr(expr->binary.left, out); + fprintf(out, " %.*s ", (int)expr->binary.op.length, expr->binary.op.start); + emit_const_expr(expr->binary.right, out); + break; + default: + break; + } +} + +static const char* assign_mnemonic(enum TokenType op) +{ + switch (op) + { + case TOKEN_EQUAL: return "mov"; + case TOKEN_PLUS_EQUAL: return "add"; + case TOKEN_MINUS_EQUAL: return "sub"; + case TOKEN_STAR_EQUAL: return "imul"; + default: return NULL; + } +} + +struct FloatTable +{ + struct Token* items; + size_t count; + size_t capacity; +}; + +struct Emitter +{ + struct Program* program; + struct ProcDecl* proc; + struct FloatTable* floats; + FILE* out; + uint32_t label_id; +}; + +static bool is_float_register(struct Token token) +{ + if (token.length < 4 || memcmp(token.start, "xmm", 3) != 0) + return false; + + for (size_t i = 3; i < token.length; i += 1) + if (token.start[i] < '0' || token.start[i] > '9') + return false; + + return true; +} + +static size_t float_index(const struct FloatTable* floats, struct Token literal) +{ + for (size_t i = 0; i < floats->count; i += 1) + if (floats->items[i].length == literal.length + && memcmp(floats->items[i].start, literal.start, literal.length) == 0) + return i; + + return floats->count; +} + +static const char* sized_register(struct Token reg, enum StoreSize size); + +static struct Token resolve_token(struct Emitter* emitter, struct Token token) +{ + if (emitter->proc == NULL) + return token; + + for (size_t i = 0; i < emitter->proc->param_count; i += 1) + { + struct Param param = emitter->proc->params[i]; + if (param.name.length == token.length && memcmp(param.name.start, token.start, token.length) == 0) + return param.reg; + } + + return token; +} + +static struct Token text_token(const char* text) +{ + struct Token token; + token.type = TOKEN_IDENTIFIER; + token.start = text; + token.length = strlen(text); + token.line = 0; + return token; +} + +// with the logical_registers extension, r1..r14 name the general-purpose +// registers; rsp/rbp and the instruction pointer keep their dedicated names. +static const char* logical_register_base(struct Token token) +{ + static const char* registers[] = { + "rax", "rbx", "rcx", "rdx", "rsi", "rdi", + "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", + }; + + if (token.length < 2 || token.start[0] != 'r') + return NULL; + + uint32_t index = 0; + for (size_t i = 1; i < token.length; i += 1) + { + char digit = token.start[i]; + if (digit < '0' || digit > '9') + return NULL; + index = index * 10 + (uint32_t)(digit - '0'); + } + + if (index < 1 || index > sizeof(registers) / sizeof(registers[0])) + return NULL; + + return registers[index - 1]; +} + +static struct Token resolve_register(struct Emitter* emitter, struct Token token) +{ + struct Token resolved = resolve_token(emitter, token); + + if (emitter->program->config.logical_registers) + { + const char* base = logical_register_base(resolved); + if (base != NULL) + return text_token(base); + } + + return resolved; +} + +static uint64_t token_to_u64(struct Token token) +{ + if (token.length > 2 && token.start[0] == '0' && (token.start[1] == 'x' || token.start[1] == 'X')) + { + uint64_t value = 0; + for (size_t i = 2; i < token.length; i += 1) + { + char digit = token.start[i]; + uint64_t nibble = digit <= '9' ? (uint64_t)(digit - '0') + : (uint64_t)((digit | 0x20) - 'a' + 10); + value = value * 16 + nibble; + } + return value; + } + + if (token.length > 2 && token.start[0] == '0' && (token.start[1] == 'b' || token.start[1] == 'B')) + { + uint64_t value = 0; + for (size_t i = 2; i < token.length; i += 1) + value = value * 2 + (uint64_t)(token.start[i] - '0'); + return value; + } + + uint64_t value = 0; + for (size_t i = 0; i < token.length; i += 1) + value = value * 10 + (uint64_t)(token.start[i] - '0'); + + return value; +} + +static bool fold_const(struct Program* program, struct Expr* expr, uint64_t* out); + +static bool buffer_offset(struct Emitter* emitter, struct Token name, uint64_t* out_offset) +{ + struct ProcDecl* proc = emitter->proc; + uint64_t cumulative = 0; + for (size_t i = 0; i < proc->body_count; i += 1) + { + struct Statement* statement = &proc->body[i]; + if (statement->kind != STATEMENT_STACK) + continue; + + uint64_t size = 0; + fold_const(emitter->program, statement->stack.size, &size); + cumulative += size; + if (statement->stack.name.length == name.length + && memcmp(statement->stack.name.start, name.start, name.length) == 0) + { + *out_offset = cumulative; + return true; + } + } + + return false; +} + +static bool is_buffer_name(struct Emitter* emitter, struct Token token) +{ + uint64_t offset; + return emitter->proc != NULL && buffer_offset(emitter, token, &offset); +} + +static enum StoreSize size_from_int(struct Token token) +{ + switch (token_to_u64(token)) + { + case 8: return STORE_SIZE_BYTE; + case 16: return STORE_SIZE_WORD; + case 32: return STORE_SIZE_DWORD; + case 64: return STORE_SIZE_QWORD; + default: return STORE_SIZE_NONE; + } +} + +static bool tokens_equal(struct Token a, struct Token b) +{ + return a.length == b.length && memcmp(a.start, b.start, a.length) == 0; +} + +static bool token_matches(struct Token token, const char* text) +{ + size_t length = strlen(text); + return token.length == length && memcmp(token.start, text, length) == 0; +} + +static struct EnumDecl* find_enum(struct Program* program, struct Token name) +{ + for (size_t i = 0; i < program->enum_count; i += 1) + if (tokens_equal(program->enums[i].name, name)) + return &program->enums[i]; + + return NULL; +} + +static struct StructDecl* find_struct(struct Program* program, struct Token name) +{ + for (size_t i = 0; i < program->struct_count; i += 1) + if (tokens_equal(program->structs[i].name, name)) + return &program->structs[i]; + + return NULL; +} + +static uint64_t store_size_bytes(enum StoreSize size) +{ + switch (size) + { + case STORE_SIZE_BYTE: return 1; + case STORE_SIZE_WORD: return 2; + case STORE_SIZE_DWORD: return 4; + default: return 8; + } +} + +static uint64_t char_literal_value(struct Token token) +{ + if (token.length >= 4 && token.start[1] == '\\') + { + switch (token.start[2]) + { + case 'n': return 10; + case 't': return 9; + case 'r': return 13; + case '0': return 0; + case '\\': return 92; + case '\'': return 39; + default: return (unsigned char)token.start[2]; + } + } + + return (unsigned char)token.start[1]; +} + +static bool fold_member(struct Program* program, struct Expr* object, struct Token member, uint64_t* out) +{ + if (object->kind != EXPR_PRIMARY) + return false; + struct Token name = object->primary.token; + + const struct EnumDecl* enumeration = find_enum(program, name); + if (enumeration != NULL) + { + for (size_t i = 0; i < enumeration->member_count; i += 1) + if (tokens_equal(enumeration->members[i], member)) + { + *out = i; + return true; + } + return false; + } + + const struct StructDecl* layout = find_struct(program, name); + if (layout != NULL) + { + uint64_t offset = 0; + for (size_t i = 0; i < layout->field_count; i += 1) + { + if (tokens_equal(layout->fields[i].name, member)) + { + *out = offset; + return true; + } + offset += store_size_bytes(layout->fields[i].size); + } + if (token_matches(member, "size")) + { + *out = offset; + return true; + } + } + + return false; +} + +// evaluates a compile-time constant expression: integer/char literals, other +// constants, enum values and struct offsets, and + - * / over them +static bool fold_const(struct Program* program, struct Expr* expr, uint64_t* out) +{ + switch (expr->kind) + { + case EXPR_PRIMARY: + { + struct Token token = expr->primary.token; + if (token.type == TOKEN_INTEGER) + { + *out = token_to_u64(token); + return true; + } + if (token.type == TOKEN_CHAR) + { + *out = char_literal_value(token); + return true; + } + if (token.type == TOKEN_IDENTIFIER) + for (size_t i = 0; i < program->const_count; i += 1) + if (tokens_equal(program->consts[i].name, token)) + return fold_const(program, program->consts[i].value, out); + return false; + } + case EXPR_UNARY: + { + uint64_t value; + if (!fold_const(program, expr->unary.operand, &value)) + return false; + *out = 0 - value; + return true; + } + case EXPR_BINARY: + { + uint64_t left; + uint64_t right; + if (!fold_const(program, expr->binary.left, &left) + || !fold_const(program, expr->binary.right, &right)) + return false; + + switch (expr->binary.op.type) + { + case TOKEN_PLUS: *out = left + right; return true; + case TOKEN_MINUS: *out = left - right; return true; + case TOKEN_STAR: *out = left * right; return true; + case TOKEN_SLASH: *out = right != 0 ? left / right : 0; return true; + default: return false; + } + } + case EXPR_MEMBER: + return fold_member(program, expr->member.object, expr->member.member, out); + case EXPR_DEREF: + return false; + } + + return false; +} + +// an enum member folds to its 0-based index; a struct member folds to its byte +// offset (or the total size for `.size`) +static bool emit_named_member(struct Emitter* emitter, struct Token object, struct Token member) +{ + const struct EnumDecl* enumeration = find_enum(emitter->program, object); + if (enumeration != NULL) + { + for (size_t i = 0; i < enumeration->member_count; i += 1) + if (tokens_equal(enumeration->members[i], member)) + { + fprintf(emitter->out, "%zu", i); + return true; + } + } + + const struct StructDecl* layout = find_struct(emitter->program, object); + if (layout != NULL) + { + uint64_t offset = 0; + for (size_t i = 0; i < layout->field_count; i += 1) + { + if (tokens_equal(layout->fields[i].name, member)) + { + fprintf(emitter->out, "%llu", (unsigned long long)offset); + return true; + } + offset += store_size_bytes(layout->fields[i].size); + } + if (token_matches(member, "size")) + { + fprintf(emitter->out, "%llu", (unsigned long long)offset); + return true; + } + } + + return false; +} + +static bool emit_operand(struct Emitter* emitter, struct Expr* expr) +{ + switch (expr->kind) + { + case EXPR_PRIMARY: + { + struct Token token = resolve_register(emitter, expr->primary.token); + fprintf(emitter->out, "%.*s", (int)token.length, token.start); + return true; + } + case EXPR_MEMBER: + { + // a register size suffix: r1.64 -> rax, r1.8 -> al + if (expr->member.member.type == TOKEN_INTEGER && + expr->member.object->kind == EXPR_PRIMARY) + { + enum StoreSize size = size_from_int(expr->member.member); + struct Token base = resolve_register(emitter, expr->member.object->primary.token); + const char* sized = sized_register(base, size); + if (sized != NULL) + fprintf(emitter->out, "%s", sized); + else + fprintf(emitter->out, "%.*s", (int)base.length, base.start); + return true; + } + + // enum value or struct offset + if (expr->member.object->kind == EXPR_PRIMARY + && emit_named_member(emitter, expr->member.object->primary.token, expr->member.member)) + return true; + + if (!emit_operand(emitter, expr->member.object)) + return false; + fprintf(emitter->out, ".%.*s", (int)expr->member.member.length, expr->member.member.start); + return true; + } + case EXPR_UNARY: + { + uint64_t value; + if (!fold_const(emitter->program, expr, &value)) + return false; + fprintf(emitter->out, "%lld", (long long)value); + return true; + } + case EXPR_BINARY: + case EXPR_DEREF: + return false; + } + + return false; +} + +static bool is_gp_register(struct Token token) +{ + static const char* names[] = { + "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", + "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", + }; + + for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i += 1) + if (token_matches(token, names[i])) + return true; + + return false; +} + +// idiv can divide by a general-purpose register directly, but not by rax or rdx +// (it overwrites both with the quotient and remainder) nor by an immediate (it +// has no immediate form). Those divisors are copied into r11 first — already a +// volatile register here, clobbered by syscall just like rcx. +static bool divisor_uses_scratch(struct Emitter* emitter, const struct Expr* divisor) +{ + if (divisor->kind == EXPR_PRIMARY) + { + struct Token reg = resolve_register(emitter, divisor->primary.token); + if (is_gp_register(reg)) + return token_matches(reg, "rax") || token_matches(reg, "rdx"); + return true; + } + + // enum/struct members and unary minus fold to immediates; a binary or deref + // divisor is left to the direct path (unsupported, as before) + return divisor->kind == EXPR_MEMBER || divisor->kind == EXPR_UNARY; +} + +// idiv divides rdx:rax by its operand, leaving the quotient in rax and the +// remainder in rdx. The dividend arrives in dst; this loads it into rax, routes +// an awkward divisor through r11, and runs idiv. It clobbers rax, rdx and r11. +static void emit_idiv(struct Emitter* emitter, const char* dst, struct Expr* divisor) +{ + FILE* out = emitter->out; + + if (!divisor_uses_scratch(emitter, divisor)) + { + if (strcmp(dst, "rax") != 0) + fprintf(out, "\tmov rax, %s\n", dst); + fprintf(out, "\tcqo\n\tidiv "); + emit_operand(emitter, divisor); + fprintf(out, "\n"); + return; + } + + // a divisor in rax must be saved before the dividend overwrites rax + bool divisor_in_rax = divisor->kind == EXPR_PRIMARY + && token_matches(resolve_register(emitter, divisor->primary.token), "rax"); + + if (divisor_in_rax && strcmp(dst, "rax") != 0) + { + if (strcmp(dst, "r11") == 0) + fprintf(out, "\txchg rax, r11\n"); + else + fprintf(out, "\tmov r11, rax\n\tmov rax, %s\n", dst); + } + else + { + if (strcmp(dst, "rax") != 0) + fprintf(out, "\tmov rax, %s\n", dst); + fprintf(out, "\tmov r11, "); + emit_operand(emitter, divisor); + fprintf(out, "\n"); + } + + fprintf(out, "\tcqo\n\tidiv r11\n"); +} + +static void emit_division(struct Emitter* emitter, const char* dst, struct Expr* divisor) +{ + emit_idiv(emitter, dst, divisor); + if (strcmp(dst, "rax") != 0) + fprintf(emitter->out, "\tmov %s, rax\n", dst); +} + +static void emit_modulo(struct Emitter* emitter, const char* dst, struct Expr* divisor) +{ + emit_idiv(emitter, dst, divisor); + if (strcmp(dst, "rdx") != 0) + fprintf(emitter->out, "\tmov %s, rdx\n", dst); +} + +static void emit_divide(struct Emitter* emitter, struct AssignStatement* assign) +{ + if (assign->target_deref) + { + fprintf(emitter->out, "\t; TODO: unsupported division\n"); + return; + } + + struct Token target = resolve_register(emitter, assign->target); + char dst[32]; + snprintf(dst, sizeof(dst), "%.*s", (int)target.length, target.start); + + if (assign->op.type == TOKEN_PERCENT_EQUAL) + emit_modulo(emitter, dst, assign->value); + else + emit_division(emitter, dst, assign->value); +} + +// an expression can be evaluated into a register when it is a single term +// (primary or member), or a left-associative chain of binary operators whose +// right operands are plain operands (never a buffer or a nested binary) +static bool expr_supported(struct Emitter* emitter, struct Expr* expr) +{ + switch (expr->kind) + { + case EXPR_PRIMARY: + case EXPR_MEMBER: + case EXPR_DEREF: + return true; + case EXPR_UNARY: + { + uint64_t value; + return fold_const(emitter->program, expr, &value); + } + case EXPR_BINARY: + if (expr->binary.right->kind != EXPR_PRIMARY && + expr->binary.right->kind != EXPR_MEMBER && + expr->binary.right->kind != EXPR_UNARY) + return false; + if (expr->binary.right->kind == EXPR_UNARY) + { + uint64_t value; + if (!fold_const(emitter->program, expr->binary.right, &value)) + return false; + } + if (expr->binary.right->kind == EXPR_PRIMARY && + is_buffer_name(emitter, expr->binary.right->primary.token)) + return false; + return expr_supported(emitter, expr->binary.left); + } + + return false; +} + +// dst = [address], extending narrower loads into the full register. Unsigned +// loads zero-extend (movzx / a 32-bit mov); signed loads sign-extend (movsx, +// or movsxd for a dword). +static void emit_load(struct Emitter* emitter, const char* dst, struct DerefExpr* deref) +{ + FILE* out = emitter->out; + if (deref->is_signed) + { + switch (deref->size) + { + case STORE_SIZE_BYTE: + fprintf(out, "\tmovsx %s, byte [", dst); + break; + case STORE_SIZE_WORD: + fprintf(out, "\tmovsx %s, word [", dst); + break; + case STORE_SIZE_DWORD: + fprintf(out, "\tmovsxd %s, dword [", dst); + break; + default: + fprintf(out, "\tmov %s, [", dst); + break; + } + + emit_operand(emitter, deref->address); + fprintf(out, "]\n"); + return; + } + + switch (deref->size) + { + case STORE_SIZE_BYTE: + fprintf(out, "\tmovzx %s, byte [", dst); + break; + case STORE_SIZE_WORD: + fprintf(out, "\tmovzx %s, word [", dst); + break; + case STORE_SIZE_DWORD: + { + const char* dword = sized_register(text_token(dst), STORE_SIZE_DWORD); + fprintf(out, "\tmov %s, [", dword != NULL ? dword : dst); + break; + } + default: + fprintf(out, "\tmov %s, [", dst); + break; + } + + emit_operand(emitter, deref->address); + fprintf(out, "]\n"); +} + +static void emit_expr_into(struct Emitter* emitter, const char* dst, struct Expr* expr) +{ + if (expr->kind == EXPR_DEREF) + { + emit_load(emitter, dst, &expr->deref); + return; + } + + if (expr->kind == EXPR_BINARY) + { + emit_expr_into(emitter, dst, expr->binary.left); + + if (expr->binary.op.type == TOKEN_SLASH) + { + emit_division(emitter, dst, expr->binary.right); + return; + } + if (expr->binary.op.type == TOKEN_PERCENT) + { + emit_modulo(emitter, dst, expr->binary.right); + return; + } + + const char* mnemonic = + expr->binary.op.type == TOKEN_PLUS ? "add" : + expr->binary.op.type == TOKEN_MINUS ? "sub" : "imul"; + fprintf(emitter->out, "\t%s %s, ", mnemonic, dst); + emit_operand(emitter, expr->binary.right); + fprintf(emitter->out, "\n"); + return; + } + + if (expr->kind == EXPR_PRIMARY && is_buffer_name(emitter, expr->primary.token)) + { + uint64_t offset; + buffer_offset(emitter, expr->primary.token, &offset); + fprintf(emitter->out, "\tlea %s, [rbp - %llu]\n", dst, (unsigned long long)offset); + return; + } + + fprintf(emitter->out, "\tmov %s, ", dst); + emit_operand(emitter, expr); + fprintf(emitter->out, "\n"); +} + +static const char* store_size_keyword(enum StoreSize size) +{ + switch (size) + { + case STORE_SIZE_BYTE: return "byte "; + case STORE_SIZE_WORD: return "word "; + case STORE_SIZE_DWORD: return "dword "; + case STORE_SIZE_QWORD: return "qword "; + default: return ""; + } +} + +// maps a full 64-bit register to its byte/word/dword sub-register for a sized +// store, so `^byte rsi = rdx` writes `dl` rather than the whole register. +// returns NULL when the token is not a full register, or no resizing applies. +static const char* sized_register(struct Token reg, enum StoreSize size) +{ + if (size == STORE_SIZE_NONE || size == STORE_SIZE_QWORD) + return NULL; + + static const struct RegisterSizes + { + const char* quad; + const char* dword; + const char* word; + const char* byte; + } registers[] = + { + { "rax", "eax", "ax", "al" }, + { "rbx", "ebx", "bx", "bl" }, + { "rcx", "ecx", "cx", "cl" }, + { "rdx", "edx", "dx", "dl" }, + { "rsi", "esi", "si", "sil" }, + { "rdi", "edi", "di", "dil" }, + { "rbp", "ebp", "bp", "bpl" }, + { "rsp", "esp", "sp", "spl" }, + { "r8", "r8d", "r8w", "r8b" }, + { "r9", "r9d", "r9w", "r9b" }, + { "r10", "r10d", "r10w", "r10b" }, + { "r11", "r11d", "r11w", "r11b" }, + { "r12", "r12d", "r12w", "r12b" }, + { "r13", "r13d", "r13w", "r13b" }, + { "r14", "r14d", "r14w", "r14b" }, + { "r15", "r15d", "r15w", "r15b" }, + }; + + for (size_t i = 0; i < sizeof(registers) / sizeof(registers[0]); i += 1) + { + const struct RegisterSizes* entry = ®isters[i]; + size_t length = strlen(entry->quad); + if (reg.length != length || memcmp(reg.start, entry->quad, length) != 0) + continue; + + switch (size) + { + case STORE_SIZE_DWORD: return entry->dword; + case STORE_SIZE_WORD: return entry->word; + case STORE_SIZE_BYTE: return entry->byte; + default: return NULL; + } + } + + return NULL; +} + +static const char* float_mnemonic(enum TokenType op) +{ + switch (op) + { + case TOKEN_EQUAL: return "movsd"; + case TOKEN_PLUS_EQUAL: return "addsd"; + case TOKEN_MINUS_EQUAL: return "subsd"; + case TOKEN_STAR_EQUAL: return "mulsd"; + case TOKEN_SLASH_EQUAL: return "divsd"; + default: return NULL; + } +} + +static bool value_is_float(struct Emitter* emitter, const struct Expr* expr) +{ + if (expr->kind != EXPR_PRIMARY) + return false; + if (expr->primary.token.type == TOKEN_FLOAT) + return true; + + return is_float_register(resolve_register(emitter, expr->primary.token)); +} + +// floating point: xmm moves and arithmetic, conversions to/from general-purpose +// registers, and float literals loaded from their .data slot +static bool emit_float_assign(struct Emitter* emitter, struct AssignStatement* assign, + struct Token target, bool target_float) +{ + struct Expr* value = assign->value; + + // float store: ^ptr = xmm -> movsd [ptr], xmm + if (assign->target_deref) + { + if (assign->op.type != TOKEN_EQUAL || value->kind != EXPR_PRIMARY) + return false; + struct Token source = resolve_register(emitter, value->primary.token); + if (!is_float_register(source)) + return false; + fprintf(emitter->out, "\tmovsd [%.*s], %.*s\n", + (int)target.length, target.start, (int)source.length, source.start); + return true; + } + + // float load: xmm = ^ptr -> movsd xmm, [ptr] + if (value->kind == EXPR_DEREF) + { + if (!target_float || assign->op.type != TOKEN_EQUAL) + return false; + fprintf(emitter->out, "\tmovsd %.*s, [", (int)target.length, target.start); + emit_operand(emitter, value->deref.address); + fprintf(emitter->out, "]\n"); + return true; + } + + if (value->kind == EXPR_PRIMARY && value->primary.token.type == TOKEN_FLOAT) + { + if (!target_float || assign->op.type != TOKEN_EQUAL) + return false; + size_t index = float_index(emitter->floats, value->primary.token); + fprintf(emitter->out, "\tmovsd %.*s, [__float%zu]\n", + (int)target.length, target.start, index); + return true; + } + + if (value->kind != EXPR_PRIMARY) + return false; + + struct Token source = resolve_register(emitter, value->primary.token); + bool source_float = is_float_register(source); + + if (target_float && source_float) + { + const char* mnemonic = float_mnemonic(assign->op.type); + if (mnemonic == NULL) + return false; + fprintf(emitter->out, "\t%s %.*s, %.*s\n", mnemonic, + (int)target.length, target.start, (int)source.length, source.start); + return true; + } + + if (assign->op.type != TOKEN_EQUAL) + return false; + + if (target_float) + fprintf(emitter->out, "\tcvtsi2sd %.*s, %.*s\n", + (int)target.length, target.start, (int)source.length, source.start); + else + fprintf(emitter->out, "\tcvttsd2si %.*s, %.*s\n", + (int)target.length, target.start, (int)source.length, source.start); + return true; +} + +static void emit_assign(struct Emitter* emitter, struct AssignStatement* assign) +{ + struct Token float_target = resolve_register(emitter, assign->target); + if (is_float_register(float_target) || value_is_float(emitter, assign->value)) + { + if (!emit_float_assign(emitter, assign, float_target, is_float_register(float_target))) + fprintf(emitter->out, "\t; TODO: unsupported float assignment\n"); + return; + } + + if (assign->op.type == TOKEN_SLASH_EQUAL || assign->op.type == TOKEN_PERCENT_EQUAL) + { + emit_divide(emitter, assign); + return; + } + + struct Token target = resolve_register(emitter, assign->target); + + if (assign->op.type == TOKEN_EQUAL && !assign->target_deref) + { + if (!expr_supported(emitter, assign->value)) + { + fprintf(emitter->out, "\t; TODO: unsupported assignment\n"); + return; + } + + char dst[32]; + snprintf(dst, sizeof(dst), "%.*s", (int)target.length, target.start); + emit_expr_into(emitter, dst, assign->value); + return; + } + + // deref store or compound assignment: needs a plain operand, not a buffer or binary + const char* mnemonic = assign_mnemonic(assign->op.type); + bool value_is_buffer = assign->value->kind == EXPR_PRIMARY + && is_buffer_name(emitter, assign->value->primary.token); + if (mnemonic == NULL || assign->value->kind == EXPR_BINARY + || assign->value->kind == EXPR_DEREF || value_is_buffer) + { + fprintf(emitter->out, "\t; TODO: unsupported assignment\n"); + return; + } + + // adding or subtracting a constant zero (e.g. a struct field at offset 0) is a no-op + uint64_t folded; + if (!assign->target_deref + && (assign->op.type == TOKEN_PLUS_EQUAL || assign->op.type == TOKEN_MINUS_EQUAL) + && fold_const(emitter->program, assign->value, &folded) && folded == 0) + return; + + if (assign->target_deref) + { + fprintf(emitter->out, "\t%s %s[%.*s], ", mnemonic, + store_size_keyword(assign->store_size), (int)target.length, target.start); + + const char* sized = NULL; + if (assign->value->kind == EXPR_PRIMARY) + { + struct Token value = resolve_register(emitter, assign->value->primary.token); + sized = sized_register(value, assign->store_size); + if (sized != NULL) + fprintf(emitter->out, "%s", sized); + } + + if (sized == NULL) + emit_operand(emitter, assign->value); + } + else + { + fprintf(emitter->out, "\t%s %.*s, ", mnemonic, (int)target.length, target.start); + emit_operand(emitter, assign->value); + } + + fprintf(emitter->out, "\n"); +} + +static const char* jump_if_false(enum TokenType comparison) +{ + switch (comparison) + { + case TOKEN_EQUAL_EQUAL: return "jne"; + case TOKEN_BANG_EQUAL: return "je"; + case TOKEN_LESS: return "jge"; + case TOKEN_LESS_EQUAL: return "jg"; + case TOKEN_GREATER: return "jle"; + case TOKEN_GREATER_EQUAL: return "jl"; + default: return NULL; + } +} + +static struct ProcDecl* find_proc(struct Program* program, struct Token name) +{ + for (size_t i = 0; i < program->proc_count; i += 1) + { + struct ProcDecl* proc = &program->procs[i]; + if (proc->name.length == name.length && memcmp(proc->name.start, name.start, name.length) == 0) + return proc; + } + + return NULL; +} + +static void emit_call(struct Emitter* emitter, struct CallStatement* call) +{ + const struct ProcDecl* callee = find_proc(emitter->program, call->name); + if (callee == NULL || callee->param_count != call->arg_count) + { + fprintf(emitter->out, "\t; TODO: unsupported call\n"); + return; + } + + for (size_t i = 0; i < call->arg_count; i += 1) + { + if (call->args[i]->kind == EXPR_BINARY || call->args[i]->kind == EXPR_DEREF) + { + fprintf(emitter->out, "\t; TODO: unsupported call argument\n"); + continue; + } + + struct Token reg = resolve_register(emitter, callee->params[i].reg); + fprintf(emitter->out, "\tmov %.*s, ", (int)reg.length, reg.start); + emit_operand(emitter, call->args[i]); + fprintf(emitter->out, "\n"); + } + + fprintf(emitter->out, "\tcall %.*s\n", (int)call->name.length, call->name.start); +} + +static void emit_statement(struct Emitter* emitter, struct Statement* statement); + +static void emit_block(struct Emitter* emitter, struct Statement* body, size_t count) +{ + for (size_t i = 0; i < count; i += 1) + emit_statement(emitter, &body[i]); +} + +// ucomisd sets the flags like an unsigned compare, so float branches use the +// unsigned jump family (ja/jae/jb/jbe) rather than the signed one +static const char* float_jump_if_false(enum TokenType comparison) +{ + switch (comparison) + { + case TOKEN_EQUAL_EQUAL: return "jne"; + case TOKEN_BANG_EQUAL: return "je"; + case TOKEN_LESS: return "jae"; + case TOKEN_LESS_EQUAL: return "ja"; + case TOKEN_GREATER: return "jbe"; + case TOKEN_GREATER_EQUAL: return "jb"; + default: return NULL; + } +} + +static void emit_float_operand(struct Emitter* emitter, const struct Expr* expr) +{ + if (expr->kind == EXPR_PRIMARY && expr->primary.token.type == TOKEN_FLOAT) + { + fprintf(emitter->out, "[__float%zu]", float_index(emitter->floats, expr->primary.token)); + return; + } + + struct Token token = resolve_register(emitter, expr->primary.token); + fprintf(emitter->out, "%.*s", (int)token.length, token.start); +} + +// Emits the comparison for `left cmp right` and a jump to `target` taken when +// the condition is false, so the code that follows runs when it is true. Both +// if and while build on this. Returns false (after a TODO note) for a form that +// isn't supported yet. +static bool emit_branch_test(struct Emitter* emitter, struct Expr* left, + struct Token comparison, struct Expr* right, const char* target) +{ + bool is_float = value_is_float(emitter, left) || value_is_float(emitter, right); + + if (is_float) + { + const char* jump = float_jump_if_false(comparison.type); + bool left_reg = left->kind == EXPR_PRIMARY + && is_float_register(resolve_register(emitter, left->primary.token)); + if (jump == NULL || !left_reg || !value_is_float(emitter, right)) + { + fprintf(emitter->out, "\t; TODO: unsupported condition\n"); + return false; + } + + fprintf(emitter->out, "\tucomisd "); + emit_float_operand(emitter, left); + fprintf(emitter->out, ", "); + emit_float_operand(emitter, right); + fprintf(emitter->out, "\n\t%s %s\n", jump, target); + return true; + } + + const char* jump = jump_if_false(comparison.type); + if (jump == NULL + || left->kind == EXPR_BINARY || left->kind == EXPR_DEREF + || right->kind == EXPR_BINARY || right->kind == EXPR_DEREF) + { + fprintf(emitter->out, "\t; TODO: unsupported condition\n"); + return false; + } + + fprintf(emitter->out, "\tcmp "); + emit_operand(emitter, left); + fprintf(emitter->out, ", "); + emit_operand(emitter, right); + fprintf(emitter->out, "\n\t%s %s\n", jump, target); + return true; +} + +static void emit_if(struct Emitter* emitter, struct IfStatement* branch) +{ + bool has_else = branch->else_count > 0; + + uint32_t id = emitter->label_id; + emitter->label_id += 1; + + char target[32]; + snprintf(target, sizeof(target), ".if_%s_%u", has_else ? "else" : "end", id); + + if (!emit_branch_test(emitter, branch->left, branch->comparison, branch->right, target)) + return; + + emit_block(emitter, branch->body, branch->body_count); + + if (has_else) + { + fprintf(emitter->out, "\tjmp .if_end_%u\n", id); + fprintf(emitter->out, ".if_else_%u:\n", id); + emit_block(emitter, branch->else_body, branch->else_count); + } + + fprintf(emitter->out, ".if_end_%u:\n", id); +} + +static void emit_while(struct Emitter* emitter, struct WhileStatement* loop) +{ + char top[64]; + char end[64]; + + if (loop->named) + { + snprintf(top, sizeof(top), ".%.*s", (int)loop->name.length, loop->name.start); + snprintf(end, sizeof(end), ".%.*s_end", (int)loop->name.length, loop->name.start); + } + else + { + uint32_t id = emitter->label_id; + emitter->label_id += 1; + snprintf(top, sizeof(top), ".while_%u", id); + snprintf(end, sizeof(end), ".while_end_%u", id); + } + + fprintf(emitter->out, "%s:\n", top); + + if (!emit_branch_test(emitter, loop->left, loop->comparison, loop->right, end)) + return; + + emit_block(emitter, loop->body, loop->body_count); + + fprintf(emitter->out, "\tjmp %s\n", top); + fprintf(emitter->out, "%s:\n", end); +} + +static void emit_statement(struct Emitter* emitter, struct Statement* statement) +{ + FILE* out = emitter->out; + switch (statement->kind) + { + case STATEMENT_ASSIGN: + emit_assign(emitter, &statement->assign); + break; + case STATEMENT_LABEL: + fprintf(out, "%.*s:\n", (int)statement->label.name.length, statement->label.name.start); + break; + case STATEMENT_GOTO: + fprintf(out, "\tjmp %.*s\n", (int)statement->jump.label.length, statement->jump.label.start); + break; + case STATEMENT_SYSCALL: + fprintf(out, "\tsyscall\n"); + break; + case STATEMENT_IF: + emit_if(emitter, &statement->branch); + break; + case STATEMENT_WHILE: + emit_while(emitter, &statement->loop); + break; + case STATEMENT_CALL: + emit_call(emitter, &statement->call); + break; + case STATEMENT_STACK: + break; + default: + fprintf(out, "\t; TODO: unsupported statement\n"); + break; + } +} + +static uint64_t proc_stack_size(struct Program* program, struct ProcDecl* proc) +{ + uint64_t total = 0; + for (size_t i = 0; i < proc->body_count; i += 1) + { + struct Statement* statement = &proc->body[i]; + if (statement->kind == STATEMENT_STACK) + { + uint64_t size = 0; + fold_const(program, statement->stack.size, &size); + total += size; + } + } + + if (total % 16 != 0) + total += 16 - (total % 16); + + return total; +} + +static void collect_float(struct FloatTable* floats, struct Token token) +{ + if (token.type != TOKEN_FLOAT || float_index(floats, token) != floats->count) + return; + + if (floats->count == floats->capacity) + { + size_t capacity = floats->capacity < 8 ? 8 : floats->capacity * 2; + floats->items = realloc(floats->items, capacity * sizeof(struct Token)); + floats->capacity = capacity; + } + + floats->items[floats->count] = token; + floats->count += 1; +} + +static void collect_floats_expr(struct FloatTable* floats, struct Expr* expr) +{ + switch (expr->kind) + { + case EXPR_PRIMARY: + collect_float(floats, expr->primary.token); + break; + case EXPR_UNARY: + collect_floats_expr(floats, expr->unary.operand); + break; + case EXPR_BINARY: + collect_floats_expr(floats, expr->binary.left); + collect_floats_expr(floats, expr->binary.right); + break; + case EXPR_MEMBER: + collect_floats_expr(floats, expr->member.object); + break; + case EXPR_DEREF: + collect_floats_expr(floats, expr->deref.address); + break; + } +} + +static void collect_floats_statement(struct FloatTable* floats, struct Statement* statement) +{ + switch (statement->kind) + { + case STATEMENT_ASSIGN: + collect_floats_expr(floats, statement->assign.value); + break; + case STATEMENT_IF: + collect_floats_expr(floats, statement->branch.left); + collect_floats_expr(floats, statement->branch.right); + for (size_t i = 0; i < statement->branch.body_count; i += 1) + collect_floats_statement(floats, &statement->branch.body[i]); + for (size_t i = 0; i < statement->branch.else_count; i += 1) + collect_floats_statement(floats, &statement->branch.else_body[i]); + break; + case STATEMENT_WHILE: + collect_floats_expr(floats, statement->loop.left); + collect_floats_expr(floats, statement->loop.right); + for (size_t i = 0; i < statement->loop.body_count; i += 1) + collect_floats_statement(floats, &statement->loop.body[i]); + break; + case STATEMENT_CALL: + for (size_t i = 0; i < statement->call.arg_count; i += 1) + collect_floats_expr(floats, statement->call.args[i]); + break; + default: + break; + } +} + +static struct FloatTable collect_floats(struct Program* program) +{ + struct FloatTable floats = { NULL, 0, 0 }; + for (size_t i = 0; i < program->proc_count; i += 1) + for (size_t j = 0; j < program->procs[i].body_count; j += 1) + collect_floats_statement(&floats, &program->procs[i].body[j]); + + return floats; +} + +static void emit_proc(struct Program* program, struct FloatTable* floats, struct ProcDecl* proc, FILE* out) +{ + struct Emitter emitter; + emitter.program = program; + emitter.proc = proc; + emitter.floats = floats; + emitter.out = out; + emitter.label_id = 0; + + struct Config config = program->config; + bool is_entry = config.has_entry + && proc->name.length == config.entry.length + && memcmp(proc->name.start, config.entry.start, proc->name.length) == 0; + + fprintf(out, "%.*s:\n", (int)proc->name.length, proc->name.start); + + uint64_t stack_size = proc_stack_size(program, proc); + if (stack_size > 0) + { + fprintf(out, "\tpush rbp\n"); + fprintf(out, "\tmov rbp, rsp\n"); + fprintf(out, "\tsub rsp, %llu\n", (unsigned long long)stack_size); + } + + for (size_t i = 0; i < proc->body_count; i += 1) + emit_statement(&emitter, &proc->body[i]); + + if (!is_entry) + { + if (stack_size > 0) + fprintf(out, "\tleave\n"); + fprintf(out, "\tret\n"); + } +} + +// The instruction bodies above are plain Intel syntax, identical for every +// target assembler. Only the framing around them — the file header, constants, +// section directives, data definitions and the exported entry symbol — differs, +// so each backend supplies just those. +struct Backend +{ + void (*prologue)(const struct Program* program, FILE* out); + void (*constant)(struct ConstDecl decl, FILE* out); + void (*data_section)(FILE* out); + void (*string_data)(struct DataDecl decl, FILE* out); + void (*float_slot)(size_t index, struct Token literal, FILE* out); + void (*text_section)(FILE* out); + void (*global)(struct Token name, FILE* out); +}; + +static void nasm_prologue(const struct Program* program, FILE* out) +{ + fprintf(out, "bits %u\n", program->config.bits); +} + +static void nasm_constant(struct ConstDecl decl, FILE* out) +{ + fprintf(out, "%%define %.*s (", (int)decl.name.length, decl.name.start); + emit_const_expr(decl.value, out); + fprintf(out, ")\n"); +} + +static void nasm_data_section(FILE* out) +{ + fprintf(out, "section .data\n"); +} + +static void nasm_string_data(struct DataDecl decl, FILE* out) +{ + // the value lexeme keeps its quotes; NASM backtick strings interpret the + // same escapes, so re-wrap the inner content + fprintf(out, "%.*s: db `%.*s`\n", + (int)decl.name.length, decl.name.start, + (int)(decl.value.length - 2), decl.value.start + 1); + fprintf(out, ".len equ $ - %.*s\n", (int)decl.name.length, decl.name.start); +} + +static void nasm_float_slot(size_t index, struct Token literal, FILE* out) +{ + fprintf(out, "__float%zu: dq %.*s\n", index, (int)literal.length, literal.start); +} + +static void nasm_text_section(FILE* out) +{ + fprintf(out, "section .text\n"); +} + +static void nasm_global(struct Token name, FILE* out) +{ + fprintf(out, "global %.*s\n", (int)name.length, name.start); +} + +static const struct Backend nasm_backend = { + nasm_prologue, + nasm_constant, + nasm_data_section, + nasm_string_data, + nasm_float_slot, + nasm_text_section, + nasm_global, +}; + +static void fasm_prologue(const struct Program* program, FILE* out) +{ + fprintf(out, "format ELF%s\n", program->config.bits == 64 ? "64" : ""); +} + +static void fasm_constant(struct ConstDecl decl, FILE* out) +{ + fprintf(out, "%.*s = ", (int)decl.name.length, decl.name.start); + emit_const_expr(decl.value, out); + fprintf(out, "\n"); +} + +static void fasm_data_section(FILE* out) +{ + fprintf(out, "section '.data' writeable\n"); +} + +// fasm string literals are taken verbatim, so the escapes NASM would interpret +// are expanded here into the byte values fasm expects (db "run", 10, "run"). +static void fasm_string_data(struct DataDecl decl, FILE* out) +{ + fprintf(out, "%.*s db ", (int)decl.name.length, decl.name.start); + + const char* text = decl.value.start + 1; + size_t length = decl.value.length - 2; + bool in_quotes = false; + bool first = true; + + for (size_t i = 0; i < length; i += 1) + { + unsigned char byte = (unsigned char)text[i]; + if (byte == '\\' && i + 1 < length) + { + i += 1; + switch (text[i]) + { + case 'n': byte = '\n'; break; + case 't': byte = '\t'; break; + case 'r': byte = '\r'; break; + case '0': byte = '\0'; break; + case 'a': byte = '\a'; break; + case 'b': byte = '\b'; break; + case 'f': byte = '\f'; break; + case 'v': byte = '\v'; break; + case 'e': byte = 27; break; + default: byte = (unsigned char)text[i]; break; + } + + if (in_quotes) + { + fprintf(out, "\""); + in_quotes = false; + } + fprintf(out, "%s%u", first ? "" : ", ", byte); + first = false; + continue; + } + + if (!in_quotes) + { + fprintf(out, "%s\"", first ? "" : ", "); + in_quotes = true; + first = false; + } + fprintf(out, "%c", byte); + } + + if (in_quotes) + fprintf(out, "\""); + if (first) + fprintf(out, "\"\""); + fprintf(out, "\n"); + + fprintf(out, ".len = $ - %.*s\n", (int)decl.name.length, decl.name.start); +} + +static void fasm_float_slot(size_t index, struct Token literal, FILE* out) +{ + fprintf(out, "__float%zu dq %.*s\n", index, (int)literal.length, literal.start); +} + +static void fasm_text_section(FILE* out) +{ + fprintf(out, "section '.text' executable\n"); +} + +static void fasm_global(struct Token name, FILE* out) +{ + fprintf(out, "public %.*s\n", (int)name.length, name.start); +} + +static const struct Backend fasm_backend = { + fasm_prologue, + fasm_constant, + fasm_data_section, + fasm_string_data, + fasm_float_slot, + fasm_text_section, + fasm_global, +}; + +static void generate(struct Program* program, FILE* out, const struct Backend* backend) +{ + struct FloatTable floats = collect_floats(program); + + backend->prologue(program, out); + fprintf(out, "\n"); + + if (program->const_count > 0) + { + for (size_t i = 0; i < program->const_count; i += 1) + backend->constant(program->consts[i], out); + fprintf(out, "\n"); + } + + backend->data_section(out); + for (size_t i = 0; i < program->data_count; i += 1) + backend->string_data(program->data_decls[i], out); + for (size_t i = 0; i < floats.count; i += 1) + backend->float_slot(i, floats.items[i], out); + fprintf(out, "\n"); + + backend->text_section(out); + if (program->config.has_entry) + backend->global(program->config.entry, out); + + for (size_t i = 0; i < program->proc_count; i += 1) + { + fprintf(out, "\n"); + emit_proc(program, &floats, &program->procs[i], out); + } + + free(floats.items); +} + +void generate_nasm(struct Program* program, FILE* out) +{ + generate(program, out, &nasm_backend); +} + +void generate_fasm(struct Program* program, FILE* out) +{ + generate(program, out, &fasm_backend); +} diff --git a/src/codegen.h b/src/codegen.h new file mode 100644 index 0000000..38f37ee --- /dev/null +++ b/src/codegen.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#include "ast.h" + +void generate_nasm(struct Program* program, FILE* out); +void generate_fasm(struct Program* program, FILE* out); diff --git a/src/main.c b/src/main.c index 4153a6e..f15af79 100644 --- a/src/main.c +++ b/src/main.c @@ -5,8 +5,8 @@ #include "diag.h" #include "sema.h" #include "lexer.h" -#include "nasm.h" #include "parser.h" +#include "codegen.h" int main(int argc, char** argv) { @@ -18,9 +18,9 @@ int main(int argc, char** argv) if (result == PARSE_ERROR) return 1; - if (args.target != ASSEMBLER_NASM) + if (args.target == ASSEMBLER_MASM) { - report_error_message("only the nasm target is supported"); + report_error_message("the masm target is not implemented yet"); return 1; } @@ -62,7 +62,10 @@ int main(int argc, char** argv) } } - generate_nasm(&program, out); + if (args.target == ASSEMBLER_FASM) + generate_fasm(&program, out); + else + generate_nasm(&program, out); if (out != stdout) fclose(out); diff --git a/src/nasm.c b/src/nasm.c deleted file mode 100644 index 2a98e11..0000000 --- a/src/nasm.c +++ /dev/null @@ -1,1351 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "nasm.h" - -static void emit_const_expr(struct Expr* expr, FILE* out) -{ - switch (expr->kind) - { - case EXPR_PRIMARY: - fprintf(out, "%.*s", (int)expr->primary.token.length, expr->primary.token.start); - break; - case EXPR_UNARY: - fprintf(out, "%.*s", (int)expr->unary.op.length, expr->unary.op.start); - emit_const_expr(expr->unary.operand, out); - break; - case EXPR_BINARY: - emit_const_expr(expr->binary.left, out); - fprintf(out, " %.*s ", (int)expr->binary.op.length, expr->binary.op.start); - emit_const_expr(expr->binary.right, out); - break; - default: - break; - } -} - -static void emit_consts(struct Program* program, FILE* out) -{ - for (size_t i = 0; i < program->const_count; i += 1) - { - struct ConstDecl decl = program->consts[i]; - fprintf(out, "%%define %.*s (", (int)decl.name.length, decl.name.start); - emit_const_expr(decl.value, out); - fprintf(out, ")\n"); - } -} - -static void emit_data(struct Program* program, FILE* out) -{ - fprintf(out, "section .data\n"); - - for (size_t i = 0; i < program->data_count; i += 1) - { - struct DataDecl decl = program->data_decls[i]; - - // the value lexeme keeps its surrounding double quotes; NASM backtick - // strings interpret the same escapes, so re-wrap the inner content - fprintf(out, "%.*s: db `%.*s`\n", - (int)decl.name.length, decl.name.start, - (int)(decl.value.length - 2), decl.value.start + 1); - fprintf(out, ".len equ $ - %.*s\n", - (int)decl.name.length, decl.name.start); - } -} - -static const char* assign_mnemonic(enum TokenType op) -{ - switch (op) - { - case TOKEN_EQUAL: return "mov"; - case TOKEN_PLUS_EQUAL: return "add"; - case TOKEN_MINUS_EQUAL: return "sub"; - case TOKEN_STAR_EQUAL: return "imul"; - default: return NULL; - } -} - -struct FloatTable -{ - struct Token* items; - size_t count; - size_t capacity; -}; - -struct Emitter -{ - struct Program* program; - struct ProcDecl* proc; - struct FloatTable* floats; - FILE* out; - uint32_t label_id; -}; - -static bool is_float_register(struct Token token) -{ - if (token.length < 4 || memcmp(token.start, "xmm", 3) != 0) - return false; - - for (size_t i = 3; i < token.length; i += 1) - if (token.start[i] < '0' || token.start[i] > '9') - return false; - - return true; -} - -static size_t float_index(const struct FloatTable* floats, struct Token literal) -{ - for (size_t i = 0; i < floats->count; i += 1) - if (floats->items[i].length == literal.length - && memcmp(floats->items[i].start, literal.start, literal.length) == 0) - return i; - - return floats->count; -} - -static const char* sized_register(struct Token reg, enum StoreSize size); - -static struct Token resolve_token(struct Emitter* emitter, struct Token token) -{ - if (emitter->proc == NULL) - return token; - - for (size_t i = 0; i < emitter->proc->param_count; i += 1) - { - struct Param param = emitter->proc->params[i]; - if (param.name.length == token.length && memcmp(param.name.start, token.start, token.length) == 0) - return param.reg; - } - - return token; -} - -static struct Token text_token(const char* text) -{ - struct Token token; - token.type = TOKEN_IDENTIFIER; - token.start = text; - token.length = strlen(text); - token.line = 0; - return token; -} - -// with the logical_registers extension, r1..r14 name the general-purpose -// registers; rsp/rbp and the instruction pointer keep their dedicated names. -static const char* logical_register_base(struct Token token) -{ - static const char* registers[] = { - "rax", "rbx", "rcx", "rdx", "rsi", "rdi", - "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", - }; - - if (token.length < 2 || token.start[0] != 'r') - return NULL; - - uint32_t index = 0; - for (size_t i = 1; i < token.length; i += 1) - { - char digit = token.start[i]; - if (digit < '0' || digit > '9') - return NULL; - index = index * 10 + (uint32_t)(digit - '0'); - } - - if (index < 1 || index > sizeof(registers) / sizeof(registers[0])) - return NULL; - - return registers[index - 1]; -} - -static struct Token resolve_register(struct Emitter* emitter, struct Token token) -{ - struct Token resolved = resolve_token(emitter, token); - - if (emitter->program->config.logical_registers) - { - const char* base = logical_register_base(resolved); - if (base != NULL) - return text_token(base); - } - - return resolved; -} - -static uint64_t token_to_u64(struct Token token) -{ - if (token.length > 2 && token.start[0] == '0' && (token.start[1] == 'x' || token.start[1] == 'X')) - { - uint64_t value = 0; - for (size_t i = 2; i < token.length; i += 1) - { - char digit = token.start[i]; - uint64_t nibble = digit <= '9' ? (uint64_t)(digit - '0') - : (uint64_t)((digit | 0x20) - 'a' + 10); - value = value * 16 + nibble; - } - return value; - } - - if (token.length > 2 && token.start[0] == '0' && (token.start[1] == 'b' || token.start[1] == 'B')) - { - uint64_t value = 0; - for (size_t i = 2; i < token.length; i += 1) - value = value * 2 + (uint64_t)(token.start[i] - '0'); - return value; - } - - uint64_t value = 0; - for (size_t i = 0; i < token.length; i += 1) - value = value * 10 + (uint64_t)(token.start[i] - '0'); - - return value; -} - -static bool fold_const(struct Program* program, struct Expr* expr, uint64_t* out); - -static bool buffer_offset(struct Emitter* emitter, struct Token name, uint64_t* out_offset) -{ - struct ProcDecl* proc = emitter->proc; - uint64_t cumulative = 0; - for (size_t i = 0; i < proc->body_count; i += 1) - { - struct Statement* statement = &proc->body[i]; - if (statement->kind != STATEMENT_STACK) - continue; - - uint64_t size = 0; - fold_const(emitter->program, statement->stack.size, &size); - cumulative += size; - if (statement->stack.name.length == name.length - && memcmp(statement->stack.name.start, name.start, name.length) == 0) - { - *out_offset = cumulative; - return true; - } - } - - return false; -} - -static bool is_buffer_name(struct Emitter* emitter, struct Token token) -{ - uint64_t offset; - return emitter->proc != NULL && buffer_offset(emitter, token, &offset); -} - -static enum StoreSize size_from_int(struct Token token) -{ - switch (token_to_u64(token)) - { - case 8: return STORE_SIZE_BYTE; - case 16: return STORE_SIZE_WORD; - case 32: return STORE_SIZE_DWORD; - case 64: return STORE_SIZE_QWORD; - default: return STORE_SIZE_NONE; - } -} - -static bool tokens_equal(struct Token a, struct Token b) -{ - return a.length == b.length && memcmp(a.start, b.start, a.length) == 0; -} - -static bool token_matches(struct Token token, const char* text) -{ - size_t length = strlen(text); - return token.length == length && memcmp(token.start, text, length) == 0; -} - -static struct EnumDecl* find_enum(struct Program* program, struct Token name) -{ - for (size_t i = 0; i < program->enum_count; i += 1) - if (tokens_equal(program->enums[i].name, name)) - return &program->enums[i]; - - return NULL; -} - -static struct StructDecl* find_struct(struct Program* program, struct Token name) -{ - for (size_t i = 0; i < program->struct_count; i += 1) - if (tokens_equal(program->structs[i].name, name)) - return &program->structs[i]; - - return NULL; -} - -static uint64_t store_size_bytes(enum StoreSize size) -{ - switch (size) - { - case STORE_SIZE_BYTE: return 1; - case STORE_SIZE_WORD: return 2; - case STORE_SIZE_DWORD: return 4; - default: return 8; - } -} - -static uint64_t char_literal_value(struct Token token) -{ - if (token.length >= 4 && token.start[1] == '\\') - { - switch (token.start[2]) - { - case 'n': return 10; - case 't': return 9; - case 'r': return 13; - case '0': return 0; - case '\\': return 92; - case '\'': return 39; - default: return (unsigned char)token.start[2]; - } - } - - return (unsigned char)token.start[1]; -} - -static bool fold_member(struct Program* program, struct Expr* object, struct Token member, uint64_t* out) -{ - if (object->kind != EXPR_PRIMARY) - return false; - struct Token name = object->primary.token; - - const struct EnumDecl* enumeration = find_enum(program, name); - if (enumeration != NULL) - { - for (size_t i = 0; i < enumeration->member_count; i += 1) - if (tokens_equal(enumeration->members[i], member)) - { - *out = i; - return true; - } - return false; - } - - const struct StructDecl* layout = find_struct(program, name); - if (layout != NULL) - { - uint64_t offset = 0; - for (size_t i = 0; i < layout->field_count; i += 1) - { - if (tokens_equal(layout->fields[i].name, member)) - { - *out = offset; - return true; - } - offset += store_size_bytes(layout->fields[i].size); - } - if (token_matches(member, "size")) - { - *out = offset; - return true; - } - } - - return false; -} - -// evaluates a compile-time constant expression: integer/char literals, other -// constants, enum values and struct offsets, and + - * / over them -static bool fold_const(struct Program* program, struct Expr* expr, uint64_t* out) -{ - switch (expr->kind) - { - case EXPR_PRIMARY: - { - struct Token token = expr->primary.token; - if (token.type == TOKEN_INTEGER) - { - *out = token_to_u64(token); - return true; - } - if (token.type == TOKEN_CHAR) - { - *out = char_literal_value(token); - return true; - } - if (token.type == TOKEN_IDENTIFIER) - for (size_t i = 0; i < program->const_count; i += 1) - if (tokens_equal(program->consts[i].name, token)) - return fold_const(program, program->consts[i].value, out); - return false; - } - case EXPR_UNARY: - { - uint64_t value; - if (!fold_const(program, expr->unary.operand, &value)) - return false; - *out = 0 - value; - return true; - } - case EXPR_BINARY: - { - uint64_t left; - uint64_t right; - if (!fold_const(program, expr->binary.left, &left) - || !fold_const(program, expr->binary.right, &right)) - return false; - - switch (expr->binary.op.type) - { - case TOKEN_PLUS: *out = left + right; return true; - case TOKEN_MINUS: *out = left - right; return true; - case TOKEN_STAR: *out = left * right; return true; - case TOKEN_SLASH: *out = right != 0 ? left / right : 0; return true; - default: return false; - } - } - case EXPR_MEMBER: - return fold_member(program, expr->member.object, expr->member.member, out); - case EXPR_DEREF: - return false; - } - - return false; -} - -// an enum member folds to its 0-based index; a struct member folds to its byte -// offset (or the total size for `.size`) -static bool emit_named_member(struct Emitter* emitter, struct Token object, struct Token member) -{ - const struct EnumDecl* enumeration = find_enum(emitter->program, object); - if (enumeration != NULL) - { - for (size_t i = 0; i < enumeration->member_count; i += 1) - if (tokens_equal(enumeration->members[i], member)) - { - fprintf(emitter->out, "%zu", i); - return true; - } - } - - const struct StructDecl* layout = find_struct(emitter->program, object); - if (layout != NULL) - { - uint64_t offset = 0; - for (size_t i = 0; i < layout->field_count; i += 1) - { - if (tokens_equal(layout->fields[i].name, member)) - { - fprintf(emitter->out, "%llu", (unsigned long long)offset); - return true; - } - offset += store_size_bytes(layout->fields[i].size); - } - if (token_matches(member, "size")) - { - fprintf(emitter->out, "%llu", (unsigned long long)offset); - return true; - } - } - - return false; -} - -static bool emit_operand(struct Emitter* emitter, struct Expr* expr) -{ - switch (expr->kind) - { - case EXPR_PRIMARY: - { - struct Token token = resolve_register(emitter, expr->primary.token); - fprintf(emitter->out, "%.*s", (int)token.length, token.start); - return true; - } - case EXPR_MEMBER: - { - // a register size suffix: r1.64 -> rax, r1.8 -> al - if (expr->member.member.type == TOKEN_INTEGER && - expr->member.object->kind == EXPR_PRIMARY) - { - enum StoreSize size = size_from_int(expr->member.member); - struct Token base = resolve_register(emitter, expr->member.object->primary.token); - const char* sized = sized_register(base, size); - if (sized != NULL) - fprintf(emitter->out, "%s", sized); - else - fprintf(emitter->out, "%.*s", (int)base.length, base.start); - return true; - } - - // enum value or struct offset - if (expr->member.object->kind == EXPR_PRIMARY - && emit_named_member(emitter, expr->member.object->primary.token, expr->member.member)) - return true; - - if (!emit_operand(emitter, expr->member.object)) - return false; - fprintf(emitter->out, ".%.*s", (int)expr->member.member.length, expr->member.member.start); - return true; - } - case EXPR_UNARY: - { - uint64_t value; - if (!fold_const(emitter->program, expr, &value)) - return false; - fprintf(emitter->out, "%lld", (long long)value); - return true; - } - case EXPR_BINARY: - case EXPR_DEREF: - return false; - } - - return false; -} - -static bool is_gp_register(struct Token token) -{ - static const char* names[] = { - "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", - "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", - }; - - for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i += 1) - if (token_matches(token, names[i])) - return true; - - return false; -} - -// idiv can divide by a general-purpose register directly, but not by rax or rdx -// (it overwrites both with the quotient and remainder) nor by an immediate (it -// has no immediate form). Those divisors are copied into r11 first — already a -// volatile register here, clobbered by syscall just like rcx. -static bool divisor_uses_scratch(struct Emitter* emitter, const struct Expr* divisor) -{ - if (divisor->kind == EXPR_PRIMARY) - { - struct Token reg = resolve_register(emitter, divisor->primary.token); - if (is_gp_register(reg)) - return token_matches(reg, "rax") || token_matches(reg, "rdx"); - return true; - } - - // enum/struct members and unary minus fold to immediates; a binary or deref - // divisor is left to the direct path (unsupported, as before) - return divisor->kind == EXPR_MEMBER || divisor->kind == EXPR_UNARY; -} - -// idiv divides rdx:rax by its operand, leaving the quotient in rax and the -// remainder in rdx. The dividend arrives in dst; this loads it into rax, routes -// an awkward divisor through r11, and runs idiv. It clobbers rax, rdx and r11. -static void emit_idiv(struct Emitter* emitter, const char* dst, struct Expr* divisor) -{ - FILE* out = emitter->out; - - if (!divisor_uses_scratch(emitter, divisor)) - { - if (strcmp(dst, "rax") != 0) - fprintf(out, "\tmov rax, %s\n", dst); - fprintf(out, "\tcqo\n\tidiv "); - emit_operand(emitter, divisor); - fprintf(out, "\n"); - return; - } - - // a divisor in rax must be saved before the dividend overwrites rax - bool divisor_in_rax = divisor->kind == EXPR_PRIMARY - && token_matches(resolve_register(emitter, divisor->primary.token), "rax"); - - if (divisor_in_rax && strcmp(dst, "rax") != 0) - { - if (strcmp(dst, "r11") == 0) - fprintf(out, "\txchg rax, r11\n"); - else - fprintf(out, "\tmov r11, rax\n\tmov rax, %s\n", dst); - } - else - { - if (strcmp(dst, "rax") != 0) - fprintf(out, "\tmov rax, %s\n", dst); - fprintf(out, "\tmov r11, "); - emit_operand(emitter, divisor); - fprintf(out, "\n"); - } - - fprintf(out, "\tcqo\n\tidiv r11\n"); -} - -static void emit_division(struct Emitter* emitter, const char* dst, struct Expr* divisor) -{ - emit_idiv(emitter, dst, divisor); - if (strcmp(dst, "rax") != 0) - fprintf(emitter->out, "\tmov %s, rax\n", dst); -} - -static void emit_modulo(struct Emitter* emitter, const char* dst, struct Expr* divisor) -{ - emit_idiv(emitter, dst, divisor); - if (strcmp(dst, "rdx") != 0) - fprintf(emitter->out, "\tmov %s, rdx\n", dst); -} - -static void emit_divide(struct Emitter* emitter, struct AssignStatement* assign) -{ - if (assign->target_deref) - { - fprintf(emitter->out, "\t; TODO: unsupported division\n"); - return; - } - - struct Token target = resolve_register(emitter, assign->target); - char dst[32]; - snprintf(dst, sizeof(dst), "%.*s", (int)target.length, target.start); - - if (assign->op.type == TOKEN_PERCENT_EQUAL) - emit_modulo(emitter, dst, assign->value); - else - emit_division(emitter, dst, assign->value); -} - -// an expression can be evaluated into a register when it is a single term -// (primary or member), or a left-associative chain of binary operators whose -// right operands are plain operands (never a buffer or a nested binary) -static bool expr_supported(struct Emitter* emitter, struct Expr* expr) -{ - switch (expr->kind) - { - case EXPR_PRIMARY: - case EXPR_MEMBER: - case EXPR_DEREF: - return true; - case EXPR_UNARY: - { - uint64_t value; - return fold_const(emitter->program, expr, &value); - } - case EXPR_BINARY: - if (expr->binary.right->kind != EXPR_PRIMARY && - expr->binary.right->kind != EXPR_MEMBER && - expr->binary.right->kind != EXPR_UNARY) - return false; - if (expr->binary.right->kind == EXPR_UNARY) - { - uint64_t value; - if (!fold_const(emitter->program, expr->binary.right, &value)) - return false; - } - if (expr->binary.right->kind == EXPR_PRIMARY && - is_buffer_name(emitter, expr->binary.right->primary.token)) - return false; - return expr_supported(emitter, expr->binary.left); - } - - return false; -} - -// dst = [address], extending narrower loads into the full register. Unsigned -// loads zero-extend (movzx / a 32-bit mov); signed loads sign-extend (movsx, -// or movsxd for a dword). -static void emit_load(struct Emitter* emitter, const char* dst, struct DerefExpr* deref) -{ - FILE* out = emitter->out; - if (deref->is_signed) - { - switch (deref->size) - { - case STORE_SIZE_BYTE: - fprintf(out, "\tmovsx %s, byte [", dst); - break; - case STORE_SIZE_WORD: - fprintf(out, "\tmovsx %s, word [", dst); - break; - case STORE_SIZE_DWORD: - fprintf(out, "\tmovsxd %s, dword [", dst); - break; - default: - fprintf(out, "\tmov %s, [", dst); - break; - } - - emit_operand(emitter, deref->address); - fprintf(out, "]\n"); - return; - } - - switch (deref->size) - { - case STORE_SIZE_BYTE: - fprintf(out, "\tmovzx %s, byte [", dst); - break; - case STORE_SIZE_WORD: - fprintf(out, "\tmovzx %s, word [", dst); - break; - case STORE_SIZE_DWORD: - { - const char* dword = sized_register(text_token(dst), STORE_SIZE_DWORD); - fprintf(out, "\tmov %s, [", dword != NULL ? dword : dst); - break; - } - default: - fprintf(out, "\tmov %s, [", dst); - break; - } - - emit_operand(emitter, deref->address); - fprintf(out, "]\n"); -} - -static void emit_expr_into(struct Emitter* emitter, const char* dst, struct Expr* expr) -{ - if (expr->kind == EXPR_DEREF) - { - emit_load(emitter, dst, &expr->deref); - return; - } - - if (expr->kind == EXPR_BINARY) - { - emit_expr_into(emitter, dst, expr->binary.left); - - if (expr->binary.op.type == TOKEN_SLASH) - { - emit_division(emitter, dst, expr->binary.right); - return; - } - if (expr->binary.op.type == TOKEN_PERCENT) - { - emit_modulo(emitter, dst, expr->binary.right); - return; - } - - const char* mnemonic = - expr->binary.op.type == TOKEN_PLUS ? "add" : - expr->binary.op.type == TOKEN_MINUS ? "sub" : "imul"; - fprintf(emitter->out, "\t%s %s, ", mnemonic, dst); - emit_operand(emitter, expr->binary.right); - fprintf(emitter->out, "\n"); - return; - } - - if (expr->kind == EXPR_PRIMARY && is_buffer_name(emitter, expr->primary.token)) - { - uint64_t offset; - buffer_offset(emitter, expr->primary.token, &offset); - fprintf(emitter->out, "\tlea %s, [rbp - %llu]\n", dst, (unsigned long long)offset); - return; - } - - fprintf(emitter->out, "\tmov %s, ", dst); - emit_operand(emitter, expr); - fprintf(emitter->out, "\n"); -} - -static const char* store_size_keyword(enum StoreSize size) -{ - switch (size) - { - case STORE_SIZE_BYTE: return "byte "; - case STORE_SIZE_WORD: return "word "; - case STORE_SIZE_DWORD: return "dword "; - case STORE_SIZE_QWORD: return "qword "; - default: return ""; - } -} - -// maps a full 64-bit register to its byte/word/dword sub-register for a sized -// store, so `^byte rsi = rdx` writes `dl` rather than the whole register. -// returns NULL when the token is not a full register, or no resizing applies. -static const char* sized_register(struct Token reg, enum StoreSize size) -{ - if (size == STORE_SIZE_NONE || size == STORE_SIZE_QWORD) - return NULL; - - static const struct RegisterSizes - { - const char* quad; - const char* dword; - const char* word; - const char* byte; - } registers[] = - { - { "rax", "eax", "ax", "al" }, - { "rbx", "ebx", "bx", "bl" }, - { "rcx", "ecx", "cx", "cl" }, - { "rdx", "edx", "dx", "dl" }, - { "rsi", "esi", "si", "sil" }, - { "rdi", "edi", "di", "dil" }, - { "rbp", "ebp", "bp", "bpl" }, - { "rsp", "esp", "sp", "spl" }, - { "r8", "r8d", "r8w", "r8b" }, - { "r9", "r9d", "r9w", "r9b" }, - { "r10", "r10d", "r10w", "r10b" }, - { "r11", "r11d", "r11w", "r11b" }, - { "r12", "r12d", "r12w", "r12b" }, - { "r13", "r13d", "r13w", "r13b" }, - { "r14", "r14d", "r14w", "r14b" }, - { "r15", "r15d", "r15w", "r15b" }, - }; - - for (size_t i = 0; i < sizeof(registers) / sizeof(registers[0]); i += 1) - { - const struct RegisterSizes* entry = ®isters[i]; - size_t length = strlen(entry->quad); - if (reg.length != length || memcmp(reg.start, entry->quad, length) != 0) - continue; - - switch (size) - { - case STORE_SIZE_DWORD: return entry->dword; - case STORE_SIZE_WORD: return entry->word; - case STORE_SIZE_BYTE: return entry->byte; - default: return NULL; - } - } - - return NULL; -} - -static const char* float_mnemonic(enum TokenType op) -{ - switch (op) - { - case TOKEN_EQUAL: return "movsd"; - case TOKEN_PLUS_EQUAL: return "addsd"; - case TOKEN_MINUS_EQUAL: return "subsd"; - case TOKEN_STAR_EQUAL: return "mulsd"; - case TOKEN_SLASH_EQUAL: return "divsd"; - default: return NULL; - } -} - -static bool value_is_float(struct Emitter* emitter, const struct Expr* expr) -{ - if (expr->kind != EXPR_PRIMARY) - return false; - if (expr->primary.token.type == TOKEN_FLOAT) - return true; - - return is_float_register(resolve_register(emitter, expr->primary.token)); -} - -// floating point: xmm moves and arithmetic, conversions to/from general-purpose -// registers, and float literals loaded from their .data slot -static bool emit_float_assign(struct Emitter* emitter, struct AssignStatement* assign, - struct Token target, bool target_float) -{ - struct Expr* value = assign->value; - - // float store: ^ptr = xmm -> movsd [ptr], xmm - if (assign->target_deref) - { - if (assign->op.type != TOKEN_EQUAL || value->kind != EXPR_PRIMARY) - return false; - struct Token source = resolve_register(emitter, value->primary.token); - if (!is_float_register(source)) - return false; - fprintf(emitter->out, "\tmovsd [%.*s], %.*s\n", - (int)target.length, target.start, (int)source.length, source.start); - return true; - } - - // float load: xmm = ^ptr -> movsd xmm, [ptr] - if (value->kind == EXPR_DEREF) - { - if (!target_float || assign->op.type != TOKEN_EQUAL) - return false; - fprintf(emitter->out, "\tmovsd %.*s, [", (int)target.length, target.start); - emit_operand(emitter, value->deref.address); - fprintf(emitter->out, "]\n"); - return true; - } - - if (value->kind == EXPR_PRIMARY && value->primary.token.type == TOKEN_FLOAT) - { - if (!target_float || assign->op.type != TOKEN_EQUAL) - return false; - size_t index = float_index(emitter->floats, value->primary.token); - fprintf(emitter->out, "\tmovsd %.*s, [__float%zu]\n", - (int)target.length, target.start, index); - return true; - } - - if (value->kind != EXPR_PRIMARY) - return false; - - struct Token source = resolve_register(emitter, value->primary.token); - bool source_float = is_float_register(source); - - if (target_float && source_float) - { - const char* mnemonic = float_mnemonic(assign->op.type); - if (mnemonic == NULL) - return false; - fprintf(emitter->out, "\t%s %.*s, %.*s\n", mnemonic, - (int)target.length, target.start, (int)source.length, source.start); - return true; - } - - if (assign->op.type != TOKEN_EQUAL) - return false; - - if (target_float) - fprintf(emitter->out, "\tcvtsi2sd %.*s, %.*s\n", - (int)target.length, target.start, (int)source.length, source.start); - else - fprintf(emitter->out, "\tcvttsd2si %.*s, %.*s\n", - (int)target.length, target.start, (int)source.length, source.start); - return true; -} - -static void emit_assign(struct Emitter* emitter, struct AssignStatement* assign) -{ - struct Token float_target = resolve_register(emitter, assign->target); - if (is_float_register(float_target) || value_is_float(emitter, assign->value)) - { - if (!emit_float_assign(emitter, assign, float_target, is_float_register(float_target))) - fprintf(emitter->out, "\t; TODO: unsupported float assignment\n"); - return; - } - - if (assign->op.type == TOKEN_SLASH_EQUAL || assign->op.type == TOKEN_PERCENT_EQUAL) - { - emit_divide(emitter, assign); - return; - } - - struct Token target = resolve_register(emitter, assign->target); - - if (assign->op.type == TOKEN_EQUAL && !assign->target_deref) - { - if (!expr_supported(emitter, assign->value)) - { - fprintf(emitter->out, "\t; TODO: unsupported assignment\n"); - return; - } - - char dst[32]; - snprintf(dst, sizeof(dst), "%.*s", (int)target.length, target.start); - emit_expr_into(emitter, dst, assign->value); - return; - } - - // deref store or compound assignment: needs a plain operand, not a buffer or binary - const char* mnemonic = assign_mnemonic(assign->op.type); - bool value_is_buffer = assign->value->kind == EXPR_PRIMARY - && is_buffer_name(emitter, assign->value->primary.token); - if (mnemonic == NULL || assign->value->kind == EXPR_BINARY - || assign->value->kind == EXPR_DEREF || value_is_buffer) - { - fprintf(emitter->out, "\t; TODO: unsupported assignment\n"); - return; - } - - // adding or subtracting a constant zero (e.g. a struct field at offset 0) is a no-op - uint64_t folded; - if (!assign->target_deref - && (assign->op.type == TOKEN_PLUS_EQUAL || assign->op.type == TOKEN_MINUS_EQUAL) - && fold_const(emitter->program, assign->value, &folded) && folded == 0) - return; - - if (assign->target_deref) - { - fprintf(emitter->out, "\t%s %s[%.*s], ", mnemonic, - store_size_keyword(assign->store_size), (int)target.length, target.start); - - const char* sized = NULL; - if (assign->value->kind == EXPR_PRIMARY) - { - struct Token value = resolve_register(emitter, assign->value->primary.token); - sized = sized_register(value, assign->store_size); - if (sized != NULL) - fprintf(emitter->out, "%s", sized); - } - - if (sized == NULL) - emit_operand(emitter, assign->value); - } - else - { - fprintf(emitter->out, "\t%s %.*s, ", mnemonic, (int)target.length, target.start); - emit_operand(emitter, assign->value); - } - - fprintf(emitter->out, "\n"); -} - -static const char* jump_if_false(enum TokenType comparison) -{ - switch (comparison) - { - case TOKEN_EQUAL_EQUAL: return "jne"; - case TOKEN_BANG_EQUAL: return "je"; - case TOKEN_LESS: return "jge"; - case TOKEN_LESS_EQUAL: return "jg"; - case TOKEN_GREATER: return "jle"; - case TOKEN_GREATER_EQUAL: return "jl"; - default: return NULL; - } -} - -static struct ProcDecl* find_proc(struct Program* program, struct Token name) -{ - for (size_t i = 0; i < program->proc_count; i += 1) - { - struct ProcDecl* proc = &program->procs[i]; - if (proc->name.length == name.length && memcmp(proc->name.start, name.start, name.length) == 0) - return proc; - } - - return NULL; -} - -static void emit_call(struct Emitter* emitter, struct CallStatement* call) -{ - const struct ProcDecl* callee = find_proc(emitter->program, call->name); - if (callee == NULL || callee->param_count != call->arg_count) - { - fprintf(emitter->out, "\t; TODO: unsupported call\n"); - return; - } - - for (size_t i = 0; i < call->arg_count; i += 1) - { - if (call->args[i]->kind == EXPR_BINARY || call->args[i]->kind == EXPR_DEREF) - { - fprintf(emitter->out, "\t; TODO: unsupported call argument\n"); - continue; - } - - struct Token reg = resolve_register(emitter, callee->params[i].reg); - fprintf(emitter->out, "\tmov %.*s, ", (int)reg.length, reg.start); - emit_operand(emitter, call->args[i]); - fprintf(emitter->out, "\n"); - } - - fprintf(emitter->out, "\tcall %.*s\n", (int)call->name.length, call->name.start); -} - -static void emit_statement(struct Emitter* emitter, struct Statement* statement); - -static void emit_block(struct Emitter* emitter, struct Statement* body, size_t count) -{ - for (size_t i = 0; i < count; i += 1) - emit_statement(emitter, &body[i]); -} - -// ucomisd sets the flags like an unsigned compare, so float branches use the -// unsigned jump family (ja/jae/jb/jbe) rather than the signed one -static const char* float_jump_if_false(enum TokenType comparison) -{ - switch (comparison) - { - case TOKEN_EQUAL_EQUAL: return "jne"; - case TOKEN_BANG_EQUAL: return "je"; - case TOKEN_LESS: return "jae"; - case TOKEN_LESS_EQUAL: return "ja"; - case TOKEN_GREATER: return "jbe"; - case TOKEN_GREATER_EQUAL: return "jb"; - default: return NULL; - } -} - -static void emit_float_operand(struct Emitter* emitter, const struct Expr* expr) -{ - if (expr->kind == EXPR_PRIMARY && expr->primary.token.type == TOKEN_FLOAT) - { - fprintf(emitter->out, "[__float%zu]", float_index(emitter->floats, expr->primary.token)); - return; - } - - struct Token token = resolve_register(emitter, expr->primary.token); - fprintf(emitter->out, "%.*s", (int)token.length, token.start); -} - -// Emits the comparison for `left cmp right` and a jump to `target` taken when -// the condition is false, so the code that follows runs when it is true. Both -// if and while build on this. Returns false (after a TODO note) for a form that -// isn't supported yet. -static bool emit_branch_test(struct Emitter* emitter, struct Expr* left, - struct Token comparison, struct Expr* right, const char* target) -{ - bool is_float = value_is_float(emitter, left) || value_is_float(emitter, right); - - if (is_float) - { - const char* jump = float_jump_if_false(comparison.type); - bool left_reg = left->kind == EXPR_PRIMARY - && is_float_register(resolve_register(emitter, left->primary.token)); - if (jump == NULL || !left_reg || !value_is_float(emitter, right)) - { - fprintf(emitter->out, "\t; TODO: unsupported condition\n"); - return false; - } - - fprintf(emitter->out, "\tucomisd "); - emit_float_operand(emitter, left); - fprintf(emitter->out, ", "); - emit_float_operand(emitter, right); - fprintf(emitter->out, "\n\t%s %s\n", jump, target); - return true; - } - - const char* jump = jump_if_false(comparison.type); - if (jump == NULL - || left->kind == EXPR_BINARY || left->kind == EXPR_DEREF - || right->kind == EXPR_BINARY || right->kind == EXPR_DEREF) - { - fprintf(emitter->out, "\t; TODO: unsupported condition\n"); - return false; - } - - fprintf(emitter->out, "\tcmp "); - emit_operand(emitter, left); - fprintf(emitter->out, ", "); - emit_operand(emitter, right); - fprintf(emitter->out, "\n\t%s %s\n", jump, target); - return true; -} - -static void emit_if(struct Emitter* emitter, struct IfStatement* branch) -{ - bool has_else = branch->else_count > 0; - - uint32_t id = emitter->label_id; - emitter->label_id += 1; - - char target[32]; - snprintf(target, sizeof(target), ".if_%s_%u", has_else ? "else" : "end", id); - - if (!emit_branch_test(emitter, branch->left, branch->comparison, branch->right, target)) - return; - - emit_block(emitter, branch->body, branch->body_count); - - if (has_else) - { - fprintf(emitter->out, "\tjmp .if_end_%u\n", id); - fprintf(emitter->out, ".if_else_%u:\n", id); - emit_block(emitter, branch->else_body, branch->else_count); - } - - fprintf(emitter->out, ".if_end_%u:\n", id); -} - -static void emit_while(struct Emitter* emitter, struct WhileStatement* loop) -{ - uint32_t id = emitter->label_id; - emitter->label_id += 1; - - char target[32]; - snprintf(target, sizeof(target), ".while_end_%u", id); - - fprintf(emitter->out, ".while_%u:\n", id); - - if (!emit_branch_test(emitter, loop->left, loop->comparison, loop->right, target)) - return; - - emit_block(emitter, loop->body, loop->body_count); - - fprintf(emitter->out, "\tjmp .while_%u\n", id); - fprintf(emitter->out, ".while_end_%u:\n", id); -} - -static void emit_statement(struct Emitter* emitter, struct Statement* statement) -{ - FILE* out = emitter->out; - switch (statement->kind) - { - case STATEMENT_ASSIGN: - emit_assign(emitter, &statement->assign); - break; - case STATEMENT_LABEL: - fprintf(out, "%.*s:\n", (int)statement->label.name.length, statement->label.name.start); - break; - case STATEMENT_GOTO: - fprintf(out, "\tjmp %.*s\n", (int)statement->jump.label.length, statement->jump.label.start); - break; - case STATEMENT_SYSCALL: - fprintf(out, "\tsyscall\n"); - break; - case STATEMENT_IF: - emit_if(emitter, &statement->branch); - break; - case STATEMENT_WHILE: - emit_while(emitter, &statement->loop); - break; - case STATEMENT_CALL: - emit_call(emitter, &statement->call); - break; - case STATEMENT_STACK: - break; - default: - fprintf(out, "\t; TODO: unsupported statement\n"); - break; - } -} - -static uint64_t proc_stack_size(struct Program* program, struct ProcDecl* proc) -{ - uint64_t total = 0; - for (size_t i = 0; i < proc->body_count; i += 1) - { - struct Statement* statement = &proc->body[i]; - if (statement->kind == STATEMENT_STACK) - { - uint64_t size = 0; - fold_const(program, statement->stack.size, &size); - total += size; - } - } - - if (total % 16 != 0) - total += 16 - (total % 16); - - return total; -} - -static void collect_float(struct FloatTable* floats, struct Token token) -{ - if (token.type != TOKEN_FLOAT || float_index(floats, token) != floats->count) - return; - - if (floats->count == floats->capacity) - { - size_t capacity = floats->capacity < 8 ? 8 : floats->capacity * 2; - floats->items = realloc(floats->items, capacity * sizeof(struct Token)); - floats->capacity = capacity; - } - - floats->items[floats->count] = token; - floats->count += 1; -} - -static void collect_floats_expr(struct FloatTable* floats, struct Expr* expr) -{ - switch (expr->kind) - { - case EXPR_PRIMARY: - collect_float(floats, expr->primary.token); - break; - case EXPR_UNARY: - collect_floats_expr(floats, expr->unary.operand); - break; - case EXPR_BINARY: - collect_floats_expr(floats, expr->binary.left); - collect_floats_expr(floats, expr->binary.right); - break; - case EXPR_MEMBER: - collect_floats_expr(floats, expr->member.object); - break; - case EXPR_DEREF: - collect_floats_expr(floats, expr->deref.address); - break; - } -} - -static void collect_floats_statement(struct FloatTable* floats, struct Statement* statement) -{ - switch (statement->kind) - { - case STATEMENT_ASSIGN: - collect_floats_expr(floats, statement->assign.value); - break; - case STATEMENT_IF: - collect_floats_expr(floats, statement->branch.left); - collect_floats_expr(floats, statement->branch.right); - for (size_t i = 0; i < statement->branch.body_count; i += 1) - collect_floats_statement(floats, &statement->branch.body[i]); - for (size_t i = 0; i < statement->branch.else_count; i += 1) - collect_floats_statement(floats, &statement->branch.else_body[i]); - break; - case STATEMENT_WHILE: - collect_floats_expr(floats, statement->loop.left); - collect_floats_expr(floats, statement->loop.right); - for (size_t i = 0; i < statement->loop.body_count; i += 1) - collect_floats_statement(floats, &statement->loop.body[i]); - break; - case STATEMENT_CALL: - for (size_t i = 0; i < statement->call.arg_count; i += 1) - collect_floats_expr(floats, statement->call.args[i]); - break; - default: - break; - } -} - -static struct FloatTable collect_floats(struct Program* program) -{ - struct FloatTable floats = { NULL, 0, 0 }; - for (size_t i = 0; i < program->proc_count; i += 1) - for (size_t j = 0; j < program->procs[i].body_count; j += 1) - collect_floats_statement(&floats, &program->procs[i].body[j]); - - return floats; -} - -static void emit_float_data(const struct FloatTable* floats, FILE* out) -{ - for (size_t i = 0; i < floats->count; i += 1) - fprintf(out, "__float%zu: dq %.*s\n", i, - (int)floats->items[i].length, floats->items[i].start); -} - -static void emit_proc(struct Program* program, struct FloatTable* floats, struct ProcDecl* proc, FILE* out) -{ - struct Emitter emitter; - emitter.program = program; - emitter.proc = proc; - emitter.floats = floats; - emitter.out = out; - emitter.label_id = 0; - - struct Config config = program->config; - bool is_entry = config.has_entry - && proc->name.length == config.entry.length - && memcmp(proc->name.start, config.entry.start, proc->name.length) == 0; - - fprintf(out, "%.*s:\n", (int)proc->name.length, proc->name.start); - - uint64_t stack_size = proc_stack_size(program, proc); - if (stack_size > 0) - { - fprintf(out, "\tpush rbp\n"); - fprintf(out, "\tmov rbp, rsp\n"); - fprintf(out, "\tsub rsp, %llu\n", (unsigned long long)stack_size); - } - - for (size_t i = 0; i < proc->body_count; i += 1) - emit_statement(&emitter, &proc->body[i]); - - if (!is_entry) - { - if (stack_size > 0) - fprintf(out, "\tleave\n"); - fprintf(out, "\tret\n"); - } -} - -void generate_nasm(struct Program* program, FILE* out) -{ - struct FloatTable floats = collect_floats(program); - - fprintf(out, "bits %u\n\n", program->config.bits); - - if (program->const_count > 0) - { - emit_consts(program, out); - fprintf(out, "\n"); - } - - emit_data(program, out); - emit_float_data(&floats, out); - fprintf(out, "\n"); - - fprintf(out, "section .text\n"); - if (program->config.has_entry) - fprintf(out, "global %.*s\n", (int)program->config.entry.length, program->config.entry.start); - - for (size_t i = 0; i < program->proc_count; i += 1) - { - fprintf(out, "\n"); - emit_proc(program, &floats, &program->procs[i], out); - } - - free(floats.items); -} diff --git a/src/nasm.h b/src/nasm.h deleted file mode 100644 index 1a93b31..0000000 --- a/src/nasm.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -#include "ast.h" - -void generate_nasm(struct Program* program, FILE* out); diff --git a/src/parser.c b/src/parser.c index ddaee72..ae7c1b3 100644 --- a/src/parser.c +++ b/src/parser.c @@ -552,6 +552,14 @@ static bool parse_if(struct Parser* parser, struct Statement* out) static bool parse_while(struct Parser* parser, struct Statement* out) { + // an optional .name makes the loop's asm labels readable (.name / .name_end) + struct Token name = { 0 }; + bool named = match_token(parser, TOKEN_DOT); + if (named && !consume(parser, TOKEN_IDENTIFIER, "expected a loop name after '.'")) + return false; + if (named) + name = parser->previous; + struct Expr* left; struct Token comparison; struct Expr* right; @@ -568,6 +576,8 @@ static bool parse_while(struct Parser* parser, struct Statement* out) } out->kind = STATEMENT_WHILE; + out->loop.named = named; + out->loop.name = name; out->loop.left = left; out->loop.comparison = comparison; out->loop.right = right; -- cgit v1.3