aboutsummaryrefslogtreecommitdiff
path: root/src/codegen
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-09-08 12:00:49 +0200
committerhachem <im@hachem.wtf>2026-09-08 12:00:49 +0200
commit9d47486aac9cd1a4422b78ee273b7a27bf7b733a (patch)
treea015067fe963f3a24e8f37a8b9cd581540b3bb8c /src/codegen
parent21be45a904a0afc767214298721bd2bbce19a054 (diff)
refactor: flatten src and migrate to meson
Diffstat (limited to 'src/codegen')
-rw-r--r--src/codegen/nasm.c1252
-rw-r--r--src/codegen/nasm.h7
2 files changed, 0 insertions, 1259 deletions
diff --git a/src/codegen/nasm.c b/src/codegen/nasm.c
deleted file mode 100644
index 198cb5c..0000000
--- a/src/codegen/nasm.c
+++ /dev/null
@@ -1,1252 +0,0 @@
-#include <stdio.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdbool.h>
-
-#include "codegen/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;
-}
-
-// idiv divides rdx:rax by its operand and leaves the quotient in rax, so a
-// division computes `dst = dst / divisor` through rax (clobbering rax and rdx).
-static void emit_division(struct Emitter* emitter, const char* dst, struct Expr* divisor)
-{
- bool dst_is_rax = strcmp(dst, "rax") == 0;
-
- if (!dst_is_rax)
- fprintf(emitter->out, "\tmov rax, %s\n", dst);
- fprintf(emitter->out, "\tcqo\n");
- fprintf(emitter->out, "\tidiv ");
- emit_operand(emitter, divisor);
- fprintf(emitter->out, "\n");
- if (!dst_is_rax)
- fprintf(emitter->out, "\tmov %s, rax\n", dst);
-}
-
-// idiv leaves the remainder in rdx, so a modulo takes its result from there
-static void emit_modulo(struct Emitter* emitter, const char* dst, struct Expr* divisor)
-{
- if (strcmp(dst, "rax") != 0)
- fprintf(emitter->out, "\tmov rax, %s\n", dst);
- fprintf(emitter->out, "\tcqo\n");
- fprintf(emitter->out, "\tidiv ");
- emit_operand(emitter, divisor);
- fprintf(emitter->out, "\n");
- 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 = &registers[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);
-}
-
-static void emit_if(struct Emitter* emitter, struct IfStatement* branch)
-{
- bool is_float = value_is_float(emitter, branch->left) || value_is_float(emitter, branch->right);
- bool has_else = branch->else_count > 0;
-
- uint32_t id = emitter->label_id;
- emitter->label_id += 1;
-
- const char* target = has_else ? ".if_else_" : ".if_end_";
-
- if (is_float)
- {
- const char* jump = float_jump_if_false(branch->comparison.type);
- bool left_reg = branch->left->kind == EXPR_PRIMARY
- && is_float_register(resolve_register(emitter, branch->left->primary.token));
- if (jump == NULL || !left_reg || !value_is_float(emitter, branch->right))
- {
- fprintf(emitter->out, "\t; TODO: unsupported if\n");
- return;
- }
-
- fprintf(emitter->out, "\tucomisd ");
- emit_float_operand(emitter, branch->left);
- fprintf(emitter->out, ", ");
- emit_float_operand(emitter, branch->right);
- fprintf(emitter->out, "\n\t%s %s%u\n", jump, target, id);
- }
- else
- {
- const char* jump = jump_if_false(branch->comparison.type);
- if (jump == NULL
- || branch->left->kind == EXPR_BINARY || branch->left->kind == EXPR_DEREF
- || branch->right->kind == EXPR_BINARY || branch->right->kind == EXPR_DEREF)
- {
- fprintf(emitter->out, "\t; TODO: unsupported if\n");
- return;
- }
-
- fprintf(emitter->out, "\tcmp ");
- emit_operand(emitter, branch->left);
- fprintf(emitter->out, ", ");
- emit_operand(emitter, branch->right);
- fprintf(emitter->out, "\n\t%s %s%u\n", jump, target, id);
- }
-
- 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_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_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_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/codegen/nasm.h b/src/codegen/nasm.h
deleted file mode 100644
index b9c2c90..0000000
--- a/src/codegen/nasm.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#pragma once
-
-#include <stdio.h>
-
-#include "parser/ast.h"
-
-void generate_nasm(struct Program* program, FILE* out);