diff options
| author | hachem <im@hachem.wtf> | 2026-09-08 12:27:07 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-09-08 12:27:07 +0200 |
| commit | b9eff51ee157b22d8fa46aeecac7b95d5ed345d9 (patch) | |
| tree | bb36f4637fe3cdbd022e3d40ee6ca4029b41740a /src | |
| parent | 60c0e084b159801a45570b56f152f0f5e9193d72 (diff) | |
feat: allow any division divisor
Diffstat (limited to 'src')
| -rw-r--r-- | src/nasm.c | 94 |
1 files changed, 76 insertions, 18 deletions
@@ -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); } |
