aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/language.md14
-rw-r--r--src/codegen/nasm.c64
-rw-r--r--src/lexer/lexer.c2
-rw-r--r--src/lexer/lexer.h1
-rw-r--r--src/parser/ast.c3
-rw-r--r--src/parser/ast.h9
-rw-r--r--src/sema/sema.c18
-rw-r--r--tests/codegen_test.c37
-rw-r--r--tests/parser_test.c40
-rw-r--r--tests/sema_test.c8
10 files changed, 191 insertions, 5 deletions
diff --git a/docs/language.md b/docs/language.md
index 4de054f..3ccdab8 100644
--- a/docs/language.md
+++ b/docs/language.md
@@ -50,7 +50,7 @@ Unknown keys, a `bits` value other than 32/64, and unknown extensions are errors
## Constants and data
-`const` names a constant integer expression — integer literals, character literals, other constants, and `+` `-` `*` `/`. Integers are decimal, `0x` hex, or `0b` binary (these forms work anywhere an integer does). `data` puts a string in `.data`; the name is its address and `.len` is its length in bytes.
+`const` names a constant integer expression — integer literals, character literals, other constants, a leading `-`, and `+` `-` `*` `/`. Integers are decimal, `0x` hex, or `0b` binary (these forms work anywhere an integer does). `data` puts a string in `.data`; the name is its address and `.len` is its length in bytes.
```hdass
const STDOUT = 1
@@ -138,7 +138,7 @@ if rax > rbx
else if rax == rbx
rdi = 0
else
- rdi = 2
+ rdi = -1
```
## Dereference (`^`)
@@ -170,10 +170,20 @@ rcx = ^dword rsi // mov ecx, [rsi] (32-bit load zero-extends)
rdx = ^rsi + 4 // load, then add 4
```
+`^signed` before the size sign-extends instead, so a narrower value keeps its sign in the full register. It needs a `byte`, `word`, or `dword` size (a full-width load has nothing to extend):
+
+```hdass
+rax = ^signed byte rsi // movsx rax, byte [rsi]
+rbx = ^signed word rsi // movsx rbx, word [rsi]
+rcx = ^signed dword rsi // movsxd rcx, dword [rsi]
+```
+
## Expressions
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.
+A leading `-` negates a term (`rax = -5`, `rbx = rax + -3`, `const OFFSET = -8`). It only applies to values that fold to a constant, so it emits a negative immediate; negating a register (`-rbx`) is not supported.
+
## Extensions
### logical_registers
diff --git a/src/codegen/nasm.c b/src/codegen/nasm.c
index 8bcdda3..198cb5c 100644
--- a/src/codegen/nasm.c
+++ b/src/codegen/nasm.c
@@ -14,6 +14,10 @@ static void emit_const_expr(struct Expr* expr, FILE* out)
case EXPR_PRIMARY:
fprintf(out, "%.*s", (int)expr->primary.token.length, expr->primary.token.start);
break;
+ case EXPR_UNARY:
+ fprintf(out, "%.*s", (int)expr->unary.op.length, expr->unary.op.start);
+ emit_const_expr(expr->unary.operand, out);
+ break;
case EXPR_BINARY:
emit_const_expr(expr->binary.left, out);
fprintf(out, " %.*s ", (int)expr->binary.op.length, expr->binary.op.start);
@@ -370,6 +374,14 @@ static bool fold_const(struct Program* program, struct Expr* expr, uint64_t* out
return fold_const(program, program->consts[i].value, out);
return false;
}
+ case EXPR_UNARY:
+ {
+ uint64_t value;
+ if (!fold_const(program, expr->unary.operand, &value))
+ return false;
+ *out = 0 - value;
+ return true;
+ }
case EXPR_BINARY:
{
uint64_t left;
@@ -470,6 +482,14 @@ static bool emit_operand(struct Emitter* emitter, struct Expr* expr)
fprintf(emitter->out, ".%.*s", (int)expr->member.member.length, expr->member.member.start);
return true;
}
+ case EXPR_UNARY:
+ {
+ uint64_t value;
+ if (!fold_const(emitter->program, expr, &value))
+ return false;
+ fprintf(emitter->out, "%lld", (long long)value);
+ return true;
+ }
case EXPR_BINARY:
case EXPR_DEREF:
return false;
@@ -536,10 +556,22 @@ static bool expr_supported(struct Emitter* emitter, struct Expr* expr)
case EXPR_MEMBER:
case EXPR_DEREF:
return true;
+ case EXPR_UNARY:
+ {
+ uint64_t value;
+ return fold_const(emitter->program, expr, &value);
+ }
case EXPR_BINARY:
if (expr->binary.right->kind != EXPR_PRIMARY &&
- expr->binary.right->kind != EXPR_MEMBER)
+ expr->binary.right->kind != EXPR_MEMBER &&
+ expr->binary.right->kind != EXPR_UNARY)
return false;
+ if (expr->binary.right->kind == EXPR_UNARY)
+ {
+ uint64_t value;
+ if (!fold_const(emitter->program, expr->binary.right, &value))
+ return false;
+ }
if (expr->binary.right->kind == EXPR_PRIMARY &&
is_buffer_name(emitter, expr->binary.right->primary.token))
return false;
@@ -549,10 +581,35 @@ static bool expr_supported(struct Emitter* emitter, struct Expr* expr)
return false;
}
-// dst = [address], zero-extending narrower loads into the full register
+// dst = [address], extending narrower loads into the full register. Unsigned
+// loads zero-extend (movzx / a 32-bit mov); signed loads sign-extend (movsx,
+// or movsxd for a dword).
static void emit_load(struct Emitter* emitter, const char* dst, struct DerefExpr* deref)
{
FILE* out = emitter->out;
+ if (deref->is_signed)
+ {
+ switch (deref->size)
+ {
+ case STORE_SIZE_BYTE:
+ fprintf(out, "\tmovsx %s, byte [", dst);
+ break;
+ case STORE_SIZE_WORD:
+ fprintf(out, "\tmovsx %s, word [", dst);
+ break;
+ case STORE_SIZE_DWORD:
+ fprintf(out, "\tmovsxd %s, dword [", dst);
+ break;
+ default:
+ fprintf(out, "\tmov %s, [", dst);
+ break;
+ }
+
+ emit_operand(emitter, deref->address);
+ fprintf(out, "]\n");
+ return;
+ }
+
switch (deref->size)
{
case STORE_SIZE_BYTE:
@@ -1073,6 +1130,9 @@ static void collect_floats_expr(struct FloatTable* floats, struct Expr* expr)
case EXPR_PRIMARY:
collect_float(floats, expr->primary.token);
break;
+ case EXPR_UNARY:
+ collect_floats_expr(floats, expr->unary.operand);
+ break;
case EXPR_BINARY:
collect_floats_expr(floats, expr->binary.left);
collect_floats_expr(floats, expr->binary.right);
diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c
index e08a458..4d468e6 100644
--- a/src/lexer/lexer.c
+++ b/src/lexer/lexer.c
@@ -45,6 +45,7 @@ static enum TokenType identifier_type(const char* start, size_t length)
{ "word", 4, TOKEN_WORD },
{ "dword", 5, TOKEN_DWORD },
{ "qword", 5, TOKEN_QWORD },
+ { "signed", 6, TOKEN_SIGNED },
};
for (size_t i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i += 1)
@@ -273,6 +274,7 @@ const char* token_type_name(enum TokenType type)
case TOKEN_WORD: return "word";
case TOKEN_DWORD: return "dword";
case TOKEN_QWORD: return "qword";
+ case TOKEN_SIGNED: return "signed";
case TOKEN_EQUAL: return "equal";
case TOKEN_PLUS: return "plus";
case TOKEN_MINUS: return "minus";
diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h
index 2bc43e0..73de02f 100644
--- a/src/lexer/lexer.h
+++ b/src/lexer/lexer.h
@@ -26,6 +26,7 @@ enum TokenType
TOKEN_WORD,
TOKEN_DWORD,
TOKEN_QWORD,
+ TOKEN_SIGNED,
TOKEN_EQUAL,
TOKEN_PLUS,
diff --git a/src/parser/ast.c b/src/parser/ast.c
index 9c78d1f..63d8a08 100644
--- a/src/parser/ast.c
+++ b/src/parser/ast.c
@@ -11,6 +11,9 @@ void free_expr(struct Expr* expr)
{
case EXPR_PRIMARY:
break;
+ case EXPR_UNARY:
+ free_expr(expr->unary.operand);
+ break;
case EXPR_BINARY:
free_expr(expr->binary.left);
free_expr(expr->binary.right);
diff --git a/src/parser/ast.h b/src/parser/ast.h
index 75d0bdd..288bddf 100644
--- a/src/parser/ast.h
+++ b/src/parser/ast.h
@@ -58,6 +58,7 @@ struct Param
enum ExprKind
{
EXPR_PRIMARY,
+ EXPR_UNARY,
EXPR_BINARY,
EXPR_MEMBER,
EXPR_DEREF,
@@ -68,6 +69,12 @@ struct PrimaryExpr
struct Token token;
};
+struct UnaryExpr
+{
+ struct Token op;
+ struct Expr* operand;
+};
+
struct BinaryExpr
{
struct Expr* left;
@@ -83,6 +90,7 @@ struct MemberExpr
struct DerefExpr
{
+ bool is_signed;
enum StoreSize size;
struct Expr* address;
};
@@ -93,6 +101,7 @@ struct Expr
union
{
struct PrimaryExpr primary;
+ struct UnaryExpr unary;
struct BinaryExpr binary;
struct MemberExpr member;
struct DerefExpr deref;
diff --git a/src/sema/sema.c b/src/sema/sema.c
index 6505077..748ccbe 100644
--- a/src/sema/sema.c
+++ b/src/sema/sema.c
@@ -103,10 +103,11 @@ static bool is_program_const(const struct Program* program, struct Token name)
return false;
}
-static struct Token first_token(struct Expr* expr)
+static struct Token first_token(const struct Expr* expr)
{
switch (expr->kind)
{
+ case EXPR_UNARY: return expr->unary.op;
case EXPR_BINARY: return first_token(expr->binary.left);
case EXPR_MEMBER: return first_token(expr->member.object);
case EXPR_DEREF: return first_token(expr->deref.address);
@@ -116,6 +117,9 @@ static struct Token first_token(struct Expr* expr)
static bool check_const_value(struct Source source, struct Program* program, struct Expr* expr)
{
+ if (expr->kind == EXPR_UNARY)
+ return check_const_value(source, program, expr->unary.operand);
+
if (expr->kind == EXPR_BINARY)
{
bool left = check_const_value(source, program, expr->binary.left);
@@ -319,9 +323,18 @@ static void check_expr(struct RefCheck* check, const struct Expr* expr)
check_expr(check, expr->binary.left);
check_expr(check, expr->binary.right);
break;
+ case EXPR_UNARY:
+ check_expr(check, expr->unary.operand);
+ break;
case EXPR_DEREF:
{
const struct Expr* address = expr->deref.address;
+ if (expr->deref.is_signed
+ && expr->deref.size != STORE_SIZE_BYTE
+ && expr->deref.size != STORE_SIZE_WORD
+ && expr->deref.size != STORE_SIZE_DWORD)
+ ref_error(check, first_token(expr), "signed load requires a byte, word, or dword size");
+
if (address->kind == EXPR_PRIMARY
&& (is_register(check, address->primary.token) || is_param(check, address->primary.token)))
break;
@@ -406,6 +419,9 @@ static void check_stack_size(struct RefCheck* check, struct Expr* expr)
check_stack_size(check, expr->binary.left);
check_stack_size(check, expr->binary.right);
break;
+ case EXPR_UNARY:
+ check_stack_size(check, expr->unary.operand);
+ break;
case EXPR_MEMBER:
{
const struct Expr* object = expr->member.object;
diff --git a/tests/codegen_test.c b/tests/codegen_test.c
index db23791..e2969db 100644
--- a/tests/codegen_test.c
+++ b/tests/codegen_test.c
@@ -100,6 +100,23 @@ static void test_generate_if_else(struct TestContext* context)
free_program(&program);
}
+static void test_generate_negative(struct TestContext* context)
+{
+ struct Lexer lexer = create_lexer(
+ "const N = -8\nproc main\n{\nrax = -5\nrbx = rax + -3\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, "%define N (-8)") != NULL);
+ check(context, strstr(buffer, "mov rax, -5") != NULL);
+ check(context, strstr(buffer, "add rbx, -3") != NULL);
+
+ free_program(&program);
+}
+
static void test_generate_call(struct TestContext* context)
{
struct Lexer lexer = create_lexer(
@@ -344,6 +361,24 @@ static void test_generate_load(struct TestContext* context)
free_program(&program);
}
+static void test_generate_signed_load(struct TestContext* context)
+{
+ struct Lexer lexer = create_lexer(
+ "proc main\n{\nrax = ^signed byte rsi\nrbx = ^signed word rsi\nrcx = ^signed dword rsi\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, "movsx rax, byte [rsi]") != NULL);
+ check(context, strstr(buffer, "movsx rbx, word [rsi]") != NULL);
+ check(context, strstr(buffer, "movsxd rcx, dword [rsi]") != NULL);
+ check(context, strstr(buffer, "; TODO") == NULL);
+
+ free_program(&program);
+}
+
static void test_generate_enum_struct(struct TestContext* context)
{
struct Lexer lexer = create_lexer(
@@ -441,6 +476,7 @@ void run_codegen_tests(struct TestContext* context)
test_generate_add_zero_peephole(context);
test_generate_enum_struct(context);
test_generate_load(context);
+ test_generate_signed_load(context);
test_generate_logical_registers(context);
test_generate_logical_disabled(context);
test_generate_entry_and_bits(context);
@@ -449,6 +485,7 @@ void run_codegen_tests(struct TestContext* context)
test_generate_text(context);
test_generate_if(context);
test_generate_if_else(context);
+ test_generate_negative(context);
test_generate_call(context);
test_generate_param_substitution(context);
test_generate_divide(context);
diff --git a/tests/parser_test.c b/tests/parser_test.c
index 32ea422..e1199eb 100644
--- a/tests/parser_test.c
+++ b/tests/parser_test.c
@@ -46,6 +46,25 @@ static void test_parse_const_expr(struct TestContext* context)
free_program(&program);
}
+static void test_parse_unary(struct TestContext* context)
+{
+ struct Lexer lexer = create_lexer("const N = -8\nproc main\n{\nrax = -5\n}\n");
+ struct Program program;
+
+ check(context, parse_program(&lexer, &program));
+
+ const struct Expr* konst = program.consts[0].value;
+ check(context, konst->kind == EXPR_UNARY);
+ check(context, text_is(konst->unary.op, "-"));
+ check(context, primary_is(konst->unary.operand, "8"));
+
+ const struct Expr* value = program.procs[0].body[0].assign.value;
+ check(context, value->kind == EXPR_UNARY);
+ check(context, primary_is(value->unary.operand, "5"));
+
+ free_program(&program);
+}
+
static void test_parse_data(struct TestContext* context)
{
struct Lexer lexer = create_lexer("data msg = \"hi\"\n");
@@ -264,6 +283,25 @@ static void test_parse_sized_deref(struct TestContext* context)
free_program(&program);
}
+static void test_parse_signed_deref(struct TestContext* context)
+{
+ struct Lexer lexer = create_lexer("proc main\n{\nrax = ^signed byte rsi\nrbx = ^byte rsi\n}\n");
+ struct Program program;
+
+ check(context, parse_program(&lexer, &program));
+
+ const struct Expr* signed_load = program.procs[0].body[0].assign.value;
+ check(context, signed_load->kind == EXPR_DEREF);
+ check(context, signed_load->deref.is_signed);
+ check(context, signed_load->deref.size == STORE_SIZE_BYTE);
+
+ const struct Expr* plain_load = program.procs[0].body[1].assign.value;
+ check(context, plain_load->kind == EXPR_DEREF);
+ check(context, !plain_load->deref.is_signed);
+
+ free_program(&program);
+}
+
static void test_parse_directives(struct TestContext* context)
{
struct Lexer lexer = create_lexer("[bits: 32]\n[entry: kmain]\n[enable: logical_registers]\nproc kmain\n{\nsyscall\n}\n");
@@ -349,6 +387,7 @@ void run_parser_tests(struct TestContext* context)
{
test_parse_consts(context);
test_parse_const_expr(context);
+ test_parse_unary(context);
test_parse_data(context);
test_parse_proc_params(context);
test_parse_proc_body(context);
@@ -360,6 +399,7 @@ void run_parser_tests(struct TestContext* context)
test_parse_call(context);
test_parse_stack(context);
test_parse_sized_deref(context);
+ test_parse_signed_deref(context);
test_parse_directives(context);
test_parse_bad_directive(context);
test_parse_register_size_suffix(context);
diff --git a/tests/sema_test.c b/tests/sema_test.c
index dc01d47..643ffdf 100644
--- a/tests/sema_test.c
+++ b/tests/sema_test.c
@@ -97,6 +97,13 @@ static void test_deref_needs_register(struct TestContext* context)
check(context, analyze_source("proc main\n{\nrax = ^rsi\n}\n"));
}
+static void test_signed_load_needs_size(struct TestContext* context)
+{
+ check(context, analyze_source("proc main\n{\nrax = ^signed byte rsi\n}\n"));
+ check(context, !analyze_source("proc main\n{\nrax = ^signed rsi\n}\n"));
+ check(context, !analyze_source("proc main\n{\nrax = ^signed qword rsi\n}\n"));
+}
+
static void test_stack_size_constant(struct TestContext* context)
{
check(context, analyze_source(
@@ -152,6 +159,7 @@ void run_sema_tests(struct TestContext* context)
test_const_expr_rejects_register(context);
test_const_expr_rejects_data(context);
test_deref_needs_register(context);
+ test_signed_load_needs_size(context);
test_stack_size_constant(context);
test_enum_struct_members(context);
test_references_resolve(context);