aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-09-08 12:27:07 +0200
committerhachem <im@hachem.wtf>2026-09-08 12:27:07 +0200
commitb9eff51ee157b22d8fa46aeecac7b95d5ed345d9 (patch)
treebb36f4637fe3cdbd022e3d40ee6ca4029b41740a /src
parent60c0e084b159801a45570b56f152f0f5e9193d72 (diff)
feat: allow any division divisor
Diffstat (limited to 'src')
-rw-r--r--src/nasm.c94
1 files changed, 76 insertions, 18 deletions
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);
}