diff options
| -rw-r--r-- | docs/language.md | 9 | ||||
| -rw-r--r-- | examples/load.hdass | 17 | ||||
| -rwxr-xr-x | scripts/test_examples.sh | 1 | ||||
| -rw-r--r-- | src/codegen/nasm.c | 44 | ||||
| -rw-r--r-- | src/parser/ast.c | 3 | ||||
| -rw-r--r-- | src/parser/ast.h | 26 | ||||
| -rw-r--r-- | src/parser/parser.c | 41 | ||||
| -rw-r--r-- | src/sema/sema.c | 13 | ||||
| -rw-r--r-- | tests/codegen_test.c | 28 | ||||
| -rw-r--r-- | tests/sema_test.c | 7 |
10 files changed, 163 insertions, 26 deletions
diff --git a/docs/language.md b/docs/language.md index bfb41f3..2429838 100644 --- a/docs/language.md +++ b/docs/language.md @@ -110,7 +110,14 @@ A leading size keyword sets the width explicitly. It down-converts a full regist ^byte rsi = 10 // mov byte [rsi], 10 ``` -`^` is store-only for now; loading with `rax = ^rsi` isn't supported yet. +`^reg` is also a value — it loads from that address. A size keyword loads a narrower value and zero-extends it into the target: + +```hdass +rax = ^rsi // mov rax, [rsi] +rbx = ^byte rsi // movzx rbx, byte [rsi] +rcx = ^dword rsi // mov ecx, [rsi] (32-bit load zero-extends) +rdx = ^rsi + 4 // load, then add 4 +``` ## Expressions diff --git a/examples/load.hdass b/examples/load.hdass new file mode 100644 index 0000000..6e68ffc --- /dev/null +++ b/examples/load.hdass @@ -0,0 +1,17 @@ +[entry: main] + +// Stores 7 into a stack cell, loads it back through a pointer, and exits with it. +const SYS_EXIT = 60 + +proc main +{ + stack cell[8] + + rbx = 7 + rsi = cell // address of the cell + ^rsi = rbx // store + rdi = ^rsi // load it back + + rax = SYS_EXIT + syscall +} diff --git a/scripts/test_examples.sh b/scripts/test_examples.sh index d732e49..1c677e7 100755 --- a/scripts/test_examples.sh +++ b/scripts/test_examples.sh @@ -82,6 +82,7 @@ check branch "selects the larger of two values" examples/branch.hdass 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 load "stores then loads through a pointer" examples/load.hdass 7 "" 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 e55c791..2f8de80 100644 --- a/src/codegen/nasm.c +++ b/src/codegen/nasm.c @@ -203,6 +203,7 @@ static bool emit_operand(struct Emitter* emitter, struct Expr* expr) return true; } case EXPR_BINARY: + case EXPR_DEREF: return false; } @@ -248,6 +249,7 @@ static bool expr_supported(struct Emitter* emitter, struct Expr* expr) { case EXPR_PRIMARY: case EXPR_MEMBER: + case EXPR_DEREF: return true; case EXPR_BINARY: if (expr->binary.right->kind != EXPR_PRIMARY && @@ -262,8 +264,41 @@ static bool expr_supported(struct Emitter* emitter, struct Expr* expr) return false; } +// dst = [address], zero-extending narrower loads into the full register +static void emit_load(struct Emitter* emitter, const char* dst, struct DerefExpr* deref) +{ + FILE* out = emitter->out; + switch (deref->size) + { + case STORE_SIZE_BYTE: + fprintf(out, "\tmovzx %s, byte [", dst); + break; + case STORE_SIZE_WORD: + fprintf(out, "\tmovzx %s, word [", dst); + break; + case STORE_SIZE_DWORD: + { + const char* dword = sized_register(text_token(dst), STORE_SIZE_DWORD); + fprintf(out, "\tmov %s, [", dword != NULL ? dword : dst); + break; + } + default: + fprintf(out, "\tmov %s, [", dst); + break; + } + + emit_operand(emitter, deref->address); + fprintf(out, "]\n"); +} + static void emit_expr_into(struct Emitter* emitter, const char* dst, struct Expr* expr) { + if (expr->kind == EXPR_DEREF) + { + emit_load(emitter, dst, &expr->deref); + return; + } + if (expr->kind == EXPR_BINARY) { emit_expr_into(emitter, dst, expr->binary.left); @@ -389,7 +424,8 @@ static void emit_assign(struct Emitter* emitter, struct AssignStatement* assign) const char* mnemonic = assign_mnemonic(assign->op.type); bool value_is_buffer = assign->value->kind == EXPR_PRIMARY && is_buffer_name(emitter, assign->value->primary.token); - if (mnemonic == NULL || assign->value->kind == EXPR_BINARY || value_is_buffer) + if (mnemonic == NULL || assign->value->kind == EXPR_BINARY + || assign->value->kind == EXPR_DEREF || value_is_buffer) { fprintf(emitter->out, "\t; TODO: unsupported assignment\n"); return; @@ -458,7 +494,7 @@ static void emit_call(struct Emitter* emitter, struct CallStatement* call) for (size_t i = 0; i < call->arg_count; i += 1) { - if (call->args[i]->kind == EXPR_BINARY) + if (call->args[i]->kind == EXPR_BINARY || call->args[i]->kind == EXPR_DEREF) { fprintf(emitter->out, "\t; TODO: unsupported call argument\n"); continue; @@ -478,7 +514,9 @@ static void emit_statement(struct Emitter* emitter, struct Statement* statement) static void emit_if(struct Emitter* emitter, struct IfStatement* branch) { const char* jump = jump_if_false(branch->comparison.type); - if (jump == NULL || branch->left->kind == EXPR_BINARY || branch->right->kind == EXPR_BINARY) + if (jump == NULL + || branch->left->kind == EXPR_BINARY || branch->left->kind == EXPR_DEREF + || branch->right->kind == EXPR_BINARY || branch->right->kind == EXPR_DEREF) { fprintf(emitter->out, "\t; TODO: unsupported if\n"); return; diff --git a/src/parser/ast.c b/src/parser/ast.c index 96ee479..f9f857d 100644 --- a/src/parser/ast.c +++ b/src/parser/ast.c @@ -18,6 +18,9 @@ void free_expr(struct Expr* expr) case EXPR_MEMBER: free_expr(expr->member.object); break; + case EXPR_DEREF: + free_expr(expr->deref.address); + break; } free(expr); diff --git a/src/parser/ast.h b/src/parser/ast.h index a98f43b..fc7af2b 100644 --- a/src/parser/ast.h +++ b/src/parser/ast.h @@ -24,11 +24,21 @@ struct Param struct Token reg; }; +enum StoreSize +{ + STORE_SIZE_NONE, + STORE_SIZE_BYTE, + STORE_SIZE_WORD, + STORE_SIZE_DWORD, + STORE_SIZE_QWORD, +}; + enum ExprKind { EXPR_PRIMARY, EXPR_BINARY, EXPR_MEMBER, + EXPR_DEREF, }; struct PrimaryExpr @@ -49,6 +59,12 @@ struct MemberExpr struct Token member; }; +struct DerefExpr +{ + enum StoreSize size; + struct Expr* address; +}; + struct Expr { enum ExprKind kind; @@ -57,18 +73,10 @@ struct Expr struct PrimaryExpr primary; struct BinaryExpr binary; struct MemberExpr member; + struct DerefExpr deref; }; }; -enum StoreSize -{ - STORE_SIZE_NONE, - STORE_SIZE_BYTE, - STORE_SIZE_WORD, - STORE_SIZE_DWORD, - STORE_SIZE_QWORD, -}; - enum StatementKind { STATEMENT_ASSIGN, diff --git a/src/parser/parser.c b/src/parser/parser.c index beb6b04..969ed86 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -133,8 +133,35 @@ static struct Expr* alloc_expr(enum ExprKind kind) return expr; } +static enum StoreSize parse_store_size(struct Parser* parser) +{ + if (match_token(parser, TOKEN_BYTE)) + return STORE_SIZE_BYTE; + if (match_token(parser, TOKEN_WORD)) + return STORE_SIZE_WORD; + if (match_token(parser, TOKEN_DWORD)) + return STORE_SIZE_DWORD; + if (match_token(parser, TOKEN_QWORD)) + return STORE_SIZE_QWORD; + return STORE_SIZE_NONE; +} + static struct Expr* parse_primary(struct Parser* parser) { + if (match_token(parser, TOKEN_CARET)) + { + enum StoreSize size = parse_store_size(parser); + + struct Expr* address = parse_primary(parser); + if (address == NULL) + return NULL; + + struct Expr* deref = alloc_expr(EXPR_DEREF); + deref->deref.size = size; + deref->deref.address = address; + return deref; + } + if (check(parser, TOKEN_IDENTIFIER) || check(parser, TOKEN_INTEGER) || check(parser, TOKEN_CHAR)) { advance_parser(parser); @@ -350,19 +377,7 @@ static bool parse_statement(struct Parser* parser, struct Statement* out) } bool deref = match_token(parser, TOKEN_CARET); - - enum StoreSize store_size = STORE_SIZE_NONE; - if (deref) - { - if (match_token(parser, TOKEN_BYTE)) - store_size = STORE_SIZE_BYTE; - else if (match_token(parser, TOKEN_WORD)) - store_size = STORE_SIZE_WORD; - else if (match_token(parser, TOKEN_DWORD)) - store_size = STORE_SIZE_DWORD; - else if (match_token(parser, TOKEN_QWORD)) - store_size = STORE_SIZE_QWORD; - } + enum StoreSize store_size = deref ? parse_store_size(parser) : STORE_SIZE_NONE; if (!consume(parser, TOKEN_IDENTIFIER, "expected a statement")) return false; diff --git a/src/sema/sema.c b/src/sema/sema.c index ad2b12a..fb53bbf 100644 --- a/src/sema/sema.c +++ b/src/sema/sema.c @@ -227,6 +227,19 @@ static void check_expr(struct RefCheck* check, struct Expr* expr) check_expr(check, expr->binary.left); check_expr(check, expr->binary.right); break; + case EXPR_DEREF: + { + struct Expr* address = expr->deref.address; + if (address->kind == EXPR_PRIMARY + && (is_register(check, address->primary.token) || is_param(check, address->primary.token))) + break; + + if (address->kind == EXPR_PRIMARY) + ref_error(check, address->primary.token, "dereference address must be a register"); + else + check_expr(check, address); + break; + } case EXPR_MEMBER: { struct Expr* object = expr->member.object; diff --git a/tests/codegen_test.c b/tests/codegen_test.c index b69d79e..1cb0071 100644 --- a/tests/codegen_test.c +++ b/tests/codegen_test.c @@ -372,8 +372,36 @@ static void test_generate_logical_disabled(struct TestContext* context) free_program(&program); } +static void test_generate_load(struct TestContext* context) +{ + struct Lexer lexer = create_lexer( + "proc main\n{\nrax = ^rsi\nrbx = ^byte rsi\nrcx = ^dword rsi\nrdx = ^rsi + 4\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, [rsi]") != NULL); + check(context, strstr(buffer, "movzx rbx, byte [rsi]") != NULL); + check(context, strstr(buffer, "mov ecx, [rsi]") != NULL); + check(context, strstr(buffer, "mov rdx, [rsi]") != NULL); + check(context, strstr(buffer, "add rdx, 4") != NULL); + check(context, strstr(buffer, "; TODO") == NULL); + + free_program(&program); +} + void run_codegen_tests(struct TestContext* context) { + test_generate_load(context); test_generate_logical_registers(context); test_generate_logical_disabled(context); test_generate_entry_and_bits(context); diff --git a/tests/sema_test.c b/tests/sema_test.c index 095321d..2757e6c 100644 --- a/tests/sema_test.c +++ b/tests/sema_test.c @@ -76,6 +76,12 @@ static void test_assign_to_const(struct TestContext* context) check(context, !analyze_source("const K = 5\nproc main\n{\nK = 1\n}\n")); } +static void test_deref_needs_register(struct TestContext* context) +{ + check(context, !analyze_source("proc main\n{\nrax = ^MISSING\n}\n")); + check(context, analyze_source("proc main\n{\nrax = ^rsi\n}\n")); +} + static void test_references_resolve(struct TestContext* context) { // registers, params, consts, data (+ .len), stack buffers, labels, calls @@ -108,5 +114,6 @@ void run_sema_tests(struct TestContext* context) test_undefined_call(context); test_call_arg_count(context); test_assign_to_const(context); + test_deref_needs_register(context); test_references_resolve(context); } |
