From b9eff51ee157b22d8fa46aeecac7b95d5ed345d9 Mon Sep 17 00:00:00 2001 From: hachem Date: Tue, 8 Sep 2026 12:27:07 +0200 Subject: feat: allow any division divisor --- docs/language.md | 2 +- src/nasm.c | 94 ++++++++++++++++++++++++++++++++++++++++++---------- tests/codegen_test.c | 22 ++++++++++++ 3 files changed, 99 insertions(+), 19 deletions(-) diff --git a/docs/language.md b/docs/language.md index 8556c8b..42b178e 100644 --- a/docs/language.md +++ b/docs/language.md @@ -271,5 +271,5 @@ ld -e main program.o -o program The [README](../README.md) has a Docker setup with these tools. ## Some stinkies -Clobbering is your responsibility: `syscall` trashes `rcx` and `r11`, while a callee can trash any registers it touches, so nothing is saved automatically. `examples/fibonacci.hdass`, for example, keeps its counter in `r15` for this reason. Register widths must also match, meaning something like `rax = r1.8` would become `mov rax, al`, which will not assemble. Division has its own restrictions: `/` and `/=` use `idiv` through `rax:rdx`, clobbering both registers regardless of the destination, and the divisor cannot be `rax`, `rdx`, or an immediate, so it must first be placed in another register. Finally, the entry procedure has no `ret`; it should end with an exit syscall. +Clobbering is your responsibility: `syscall` trashes `rcx` and `r11`, while a callee can trash any registers it touches, so nothing is saved automatically. `examples/fibonacci.hdass`, for example, keeps its counter in `r15` for this reason. Register widths must also match, meaning something like `rax = r1.8` would become `mov rax, al`, which will not assemble. Division clobbers extra registers: `/` `%` and their `=` forms use `idiv` through `rax:rdx`, so both are overwritten regardless of the destination. The divisor can be anything — a register, a constant, or an immediate — but an immediate or an `rax`/`rdx` divisor is first copied into `r11`, so those also clobber `r11`. Finally, the entry procedure has no `ret`; it should end with an exit syscall. diff --git a/src/nasm.c b/src/nasm.c index 7a7d1e6..2a98e11 100644 --- a/src/nasm.c +++ b/src/nasm.c @@ -498,31 +498,89 @@ static bool emit_operand(struct Emitter* emitter, struct Expr* expr) 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) +static bool is_gp_register(struct Token token) { - bool dst_is_rax = strcmp(dst, "rax") == 0; + static const char* names[] = { + "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", + "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", + }; - 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) + 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); } -// 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"); + emit_idiv(emitter, dst, divisor); if (strcmp(dst, "rdx") != 0) fprintf(emitter->out, "\tmov %s, rdx\n", dst); } diff --git a/tests/codegen_test.c b/tests/codegen_test.c index c92dfc2..2ccba98 100644 --- a/tests/codegen_test.c +++ b/tests/codegen_test.c @@ -241,6 +241,27 @@ static void test_generate_divide_nonrax(struct TestContext* context) free_program(&program); } +static void test_generate_divide_scratch(struct TestContext* context) +{ + // an immediate or rax/rdx divisor is routed through the r11 scratch + struct Lexer lexer = create_lexer( + "proc main\n{\nrbx /= 10\nrcx /= rax\nrsi = rdi / rdx\nr11 /= rax\n}\n"); + struct Program program; + check(context, parse_program(&lexer, &program)); + + char buffer[1024]; + generate_to_buffer(&program, buffer, sizeof(buffer)); + + check(context, strstr(buffer, "mov r11, 10") != NULL); // immediate + check(context, strstr(buffer, "mov r11, rax") != NULL); // rax divisor saved + check(context, strstr(buffer, "mov r11, rdx") != NULL); // rdx divisor saved + check(context, strstr(buffer, "xchg rax, r11") != NULL); // dst is the scratch + check(context, strstr(buffer, "idiv r11") != NULL); + check(context, strstr(buffer, "; TODO") == NULL); + + free_program(&program); +} + static void test_generate_stack_frame(struct TestContext* context) { struct Lexer lexer = create_lexer("proc work\n{\nstack buffer[32]\nsyscall\n}\n"); @@ -514,6 +535,7 @@ void run_codegen_tests(struct TestContext* context) test_generate_multiply(context); test_generate_modulo(context); test_generate_divide_nonrax(context); + test_generate_divide_scratch(context); test_generate_stack_frame(context); test_generate_address_expr(context); test_generate_sized_store(context); -- cgit v1.3