diff options
| -rw-r--r-- | docs/language.md | 8 | ||||
| -rw-r--r-- | examples/mul_div.hdass | 19 | ||||
| -rwxr-xr-x | scripts/test_examples.sh | 1 | ||||
| -rw-r--r-- | src/codegen/nasm.c | 49 | ||||
| -rw-r--r-- | tests/codegen_test.c | 53 |
5 files changed, 112 insertions, 18 deletions
diff --git a/docs/language.md b/docs/language.md index a2b4ffa..bfb41f3 100644 --- a/docs/language.md +++ b/docs/language.md @@ -78,8 +78,9 @@ Written by their architecture names — `rax`–`rdi`, `rbp`, `rsp`, `r8`–`r15 ```hdass rax = SYS_WRITE // mov -rcx -= 1 // += -= *= /= -> add sub imul idiv (/= targets rax) -rdx = buffer + 31 // address math with + and - +rcx -= 1 // += -= *= /= -> add sub imul idiv +rax = rbx * rcx // + - * / in a value; / and /= use rax:rdx (see Gotchas) +rdx = buffer + 31 // address math loop: // label goto loop if rcx != 0 // == != < <= > >= ; runs the next statement only @@ -113,7 +114,7 @@ A leading size keyword sets the width explicitly. It down-converts a full regist ## Expressions -Assignment values and `if` operands: registers, integers, chars (`'0'`), constants, data names, member access (`data.len`), and `+`/`-`. Multiply and divide come from the `*=` and `/=` compound assignments, not from `*`/`/` inside a value. +Assignment values and `if` operands: registers, integers, chars (`'0'`), constants, data names, member access (`data.len`), and `+` `-` `*` `/`. Operators are left-associative and each right-hand operand must be a single term, so `a * b + c` works but `a + b * c` (a nested right operand) doesn't yet. ## Extensions @@ -160,4 +161,5 @@ The [README](../README.md) has a Docker setup with these tools. - **Clobbering is yours.** `syscall` trashes `rcx`/`r11`; a callee trashes what it touches. Nothing is saved for you — `examples/fibonacci.hdass` keeps its counter in `r15` for this reason. - **Widths must match.** `rax = r1.8` becomes `mov rax, al`, which won't assemble. +- **Division uses `rax:rdx`.** `/` and `/=` go through `idiv`, so they clobber `rax` and `rdx` regardless of the target, and the divisor can't be `rax`, `rdx`, or an immediate — put it in another register first. - **The entry procedure has no `ret`** — end it with an exit syscall.
\ No newline at end of file diff --git a/examples/mul_div.hdass b/examples/mul_div.hdass new file mode 100644 index 0000000..8c813b6 --- /dev/null +++ b/examples/mul_div.hdass @@ -0,0 +1,19 @@ +[entry: main] + +// (7 * 6) / 5 = 8 with a value-expression multiply and a non-rax division, +// returned as the exit status. +const SYS_EXIT = 60 + +proc main +{ + rbx = 7 + rcx = 6 + rbx = rbx * rcx // 42 + + rcx = 5 + rbx /= rcx // 8 + + rdi = rbx + rax = SYS_EXIT + syscall +} diff --git a/scripts/test_examples.sh b/scripts/test_examples.sh index 4a01a0e..d732e49 100755 --- a/scripts/test_examples.sh +++ b/scripts/test_examples.sh @@ -81,6 +81,7 @@ check loop_sum "sums 1..5 with a countdown loop" examples/loop_sum.hdas check branch "selects the larger of two values" examples/branch.hdass 8 "" check call "passes an argument through a proc" examples/call.hdass 21 "" check logical "logical register names (r1..r14)" examples/logical.hdass 21 "" +check mul_div "multiply and non-rax division" examples/mul_div.hdass 8 "" check fibonacci "prints the first ten Fibonacci numbers" examples/fibonacci.hdass 0 "0 1 1 diff --git a/src/codegen/nasm.c b/src/codegen/nasm.c index 0db60dd..e55c791 100644 --- a/src/codegen/nasm.c +++ b/src/codegen/nasm.c @@ -209,25 +209,39 @@ 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) +{ + bool dst_is_rax = strcmp(dst, "rax") == 0; + + 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) + fprintf(emitter->out, "\tmov %s, rax\n", dst); +} + static void emit_divide(struct Emitter* emitter, struct AssignStatement* assign) { - struct Token target = resolve_register(emitter, assign->target); - bool target_is_rax = target.length == 3 && memcmp(target.start, "rax", 3) == 0; - if (assign->target_deref || !target_is_rax) + if (assign->target_deref) { fprintf(emitter->out, "\t; TODO: unsupported division\n"); return; } - fprintf(emitter->out, "\tcqo\n"); - fprintf(emitter->out, "\tidiv "); - emit_operand(emitter, assign->value); - fprintf(emitter->out, "\n"); + struct Token target = resolve_register(emitter, assign->target); + char dst[32]; + snprintf(dst, sizeof(dst), "%.*s", (int)target.length, target.start); + emit_division(emitter, dst, assign->value); } // an expression can be evaluated into a register when it is a single term -// (primary or member), or a left-associative chain of '+'/'-' whose right -// operands are plain operands (never a buffer or a nested binary) +// (primary or member), or a left-associative chain of binary operators whose +// right operands are plain operands (never a buffer or a nested binary) static bool expr_supported(struct Emitter* emitter, struct Expr* expr) { switch (expr->kind) @@ -236,13 +250,10 @@ static bool expr_supported(struct Emitter* emitter, struct Expr* expr) case EXPR_MEMBER: return true; case EXPR_BINARY: - if (expr->binary.op.type != TOKEN_PLUS && - expr->binary.op.type != TOKEN_MINUS) - return false; - if (expr->binary.right->kind != EXPR_PRIMARY && + if (expr->binary.right->kind != EXPR_PRIMARY && expr->binary.right->kind != EXPR_MEMBER) return false; - if (expr->binary.right->kind == EXPR_PRIMARY && + if (expr->binary.right->kind == EXPR_PRIMARY && is_buffer_name(emitter, expr->binary.right->primary.token)) return false; return expr_supported(emitter, expr->binary.left); @@ -257,7 +268,15 @@ static void emit_expr_into(struct Emitter* emitter, const char* dst, struct Expr { emit_expr_into(emitter, dst, expr->binary.left); - const char* mnemonic = expr->binary.op.type == TOKEN_PLUS ? "add" : "sub"; + if (expr->binary.op.type == TOKEN_SLASH) + { + emit_division(emitter, dst, expr->binary.right); + return; + } + + const char* mnemonic = + expr->binary.op.type == TOKEN_PLUS ? "add" : + expr->binary.op.type == TOKEN_MINUS ? "sub" : "imul"; fprintf(emitter->out, "\t%s %s, ", mnemonic, dst); emit_operand(emitter, expr->binary.right); fprintf(emitter->out, "\n"); diff --git a/tests/codegen_test.c b/tests/codegen_test.c index ab771f7..b69d79e 100644 --- a/tests/codegen_test.c +++ b/tests/codegen_test.c @@ -151,6 +151,57 @@ static void test_generate_divide(struct TestContext* context) free_program(&program); } +static void test_generate_multiply(struct TestContext* context) +{ + struct Lexer lexer = create_lexer("proc main\n{\nrax = rbx * rcx\nrbx = rcx * 3 + rdx\n}\n"); + struct Program program; + check(context, parse_program(&lexer, &program)); + + FILE* out = tmpfile(); + generate_nasm(&program, out); + fflush(out); + rewind(out); + + char buffer[1024]; + size_t read = fread(buffer, 1, sizeof(buffer) - 1, out); + buffer[read] = '\0'; + fclose(out); + + check(context, strstr(buffer, "mov rax, rbx") != NULL); + check(context, strstr(buffer, "imul rax, rcx") != NULL); + check(context, strstr(buffer, "imul rbx, 3") != NULL); + check(context, strstr(buffer, "add rbx, rdx") != NULL); + check(context, strstr(buffer, "; TODO") == NULL); + + free_program(&program); +} + +static void test_generate_divide_nonrax(struct TestContext* context) +{ + // division into a register other than rax routes through rax:rdx + struct Lexer lexer = create_lexer("proc main\n{\nrbx /= rcx\nrsi = rdi / rcx\n}\n"); + struct Program program; + check(context, parse_program(&lexer, &program)); + + FILE* out = tmpfile(); + generate_nasm(&program, out); + fflush(out); + rewind(out); + + char buffer[1024]; + size_t read = fread(buffer, 1, sizeof(buffer) - 1, out); + buffer[read] = '\0'; + fclose(out); + + check(context, strstr(buffer, "mov rax, rbx") != NULL); + check(context, strstr(buffer, "idiv rcx") != NULL); + check(context, strstr(buffer, "mov rbx, rax") != NULL); + check(context, strstr(buffer, "mov rsi, rax") != 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"); @@ -333,6 +384,8 @@ void run_codegen_tests(struct TestContext* context) test_generate_call(context); test_generate_param_substitution(context); test_generate_divide(context); + test_generate_multiply(context); + test_generate_divide_nonrax(context); test_generate_stack_frame(context); test_generate_address_expr(context); test_generate_sized_store(context); |
