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 --- src/nasm.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 18 deletions(-) (limited to 'src') 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); } -- cgit v1.3