aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/language.md2
-rw-r--r--src/nasm.c94
-rw-r--r--tests/codegen_test.c22
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);