aboutsummaryrefslogtreecommitdiff
path: root/src/c8asm
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2024-02-13 14:00:32 +0100
committerThomas Voss <mail@thomasvoss.com> 2024-02-13 14:00:32 +0100
commitc23acb820f8698361c65911649f97a9320c2c2b8 (patch)
treeb4b083ff111d8bbac63e666c60c2625118d648f9 /src/c8asm
parent6dca08dfe5e1c1cfc6f40b2e421027e13821bdb6 (diff)
Complete the assembler
Diffstat (limited to 'src/c8asm')
-rw-r--r--src/c8asm/assembler.c174
-rw-r--r--src/c8asm/parser.c6
-rw-r--r--src/c8asm/parser.h6
3 files changed, 178 insertions, 8 deletions
diff --git a/src/c8asm/assembler.c b/src/c8asm/assembler.c
index 0d02555..6cdebe7 100644
--- a/src/c8asm/assembler.c
+++ b/src/c8asm/assembler.c
@@ -1,3 +1,4 @@
+#include <arpa/inet.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
@@ -16,7 +17,8 @@
# define unreachable() __builtin_unreachable()
#endif
-#define E_LEXISTS "label ‘%.*s’ has already been declared"
+#define E_LEXISTS "label ‘%.*s’ has already been declared"
+#define E_LNEXISTS "label ‘%.*s’ hasn’t been declared"
struct label {
uint16_t addr;
@@ -30,8 +32,11 @@ struct labels {
static bool u8eq(struct u8view, struct u8view);
static void pushlabel(struct labels *, struct label);
+static uint16_t getaddr(struct raw_addr);
+static struct label *getlabel(struct u8view);
static size_t i;
+static struct labels locals, globals;
bool
u8eq(struct u8view x, struct u8view y)
@@ -39,6 +44,29 @@ u8eq(struct u8view x, struct u8view y)
return x.len == y.len && memcmp(x.p, y.p, x.len) == 0;
}
+struct label *
+getlabel(struct u8view sv)
+{
+ struct labels *xs = sv.p[0] == '.' ? &locals : &globals;
+ da_foreach (xs, x) {
+ if (u8eq(x->sv, sv))
+ return x;
+ }
+ return nullptr;
+}
+
+uint16_t
+getaddr(struct raw_addr a)
+{
+ if (a.label) {
+ struct label *lbl = getlabel(a.sv);
+ if (lbl)
+ return lbl->addr;
+ die_with_off(filename, a.sv.p - baseptr, E_LNEXISTS, U8_PRI_ARGS(a.sv));
+ }
+ return a.val;
+}
+
void
pushlabel(struct labels *dst, struct label lbl)
{
@@ -55,7 +83,11 @@ pushlabel(struct labels *dst, struct label lbl)
void
assemble([[maybe_unused]] FILE *stream, struct ast ast)
{
- static struct labels locals, globals;
+#define PUT(X) \
+ do { \
+ uint16_t __x = htons(X); \
+ fwrite(&__x, 1, sizeof(__x), stream); \
+ } while (false)
da_foreach (&ast, node) {
if (node->kind == D_LABEL) {
@@ -70,5 +102,143 @@ assemble([[maybe_unused]] FILE *stream, struct ast ast)
unreachable();
}
+ da_foreach (&ast, node) {
+ if (node->kind == D_LABEL)
+ continue;
+ if (node->kind != D_INSTR)
+ unreachable();
+
+ switch (node->instr.kind) {
+ case I_ADD_I_VX:
+ PUT(0xF01E | (node->instr.args[0].val << 8));
+ break;
+ case I_ADD_VX_B:
+ PUT(0x7000 | (node->instr.args[0].val << 8)
+ | node->instr.args[1].val);
+ break;
+ case I_ADD_VX_VY:
+ PUT(0x8004 | (node->instr.args[0].val << 8)
+ | node->instr.args[1].val << 4);
+ break;
+ case I_AND:
+ PUT(0x8002 | (node->instr.args[0].val << 8)
+ | node->instr.args[1].val << 4);
+ break;
+ case I_BCD:
+ PUT(0xF033 | (node->instr.args[0].val << 8));
+ break;
+ case I_CALL:
+ PUT(0x2000 | getaddr(node->instr.args[0]));
+ break;
+ case I_CLS:
+ PUT(0x00E0);
+ break;
+ case I_DB:
+ da_foreach (&node->instr, byte)
+ fputc(*byte, stream);
+ break;
+ case I_DRW:
+ PUT(0xD000 | (node->instr.args[0].val << 8)
+ | (node->instr.args[1].val << 4) | node->instr.args[2].val);
+ break;
+ case I_HEX:
+ PUT(0xF029 | (node->instr.args[0].val << 8));
+ break;
+ case I_JP_A:
+ PUT(0x1000 | getaddr(node->instr.args[0]));
+ break;
+ case I_JP_V0_A:
+ PUT(0xB000 | getaddr(node->instr.args[0]));
+ break;
+ case I_LD_DT:
+ PUT(0xF015 | (node->instr.args[0].val << 8));
+ break;
+ case I_LD_I:
+ PUT(0xA000 | getaddr(node->instr.args[0]));
+ break;
+ case I_LD_ST:
+ PUT(0xF018 | (node->instr.args[0].val << 8));
+ break;
+ case I_LD_VX_B:
+ PUT(0x6000 | (node->instr.args[0].val << 8)
+ | node->instr.args[1].val);
+ break;
+ case I_LD_VX_DT:
+ PUT(0xF007 | (node->instr.args[0].val << 8));
+ break;
+ case I_LD_VX_K:
+ PUT(0xF00A | (node->instr.args[0].val << 8));
+ break;
+ case I_LD_VX_VY:
+ PUT(0x8000 | (node->instr.args[0].val << 8)
+ | node->instr.args[1].val << 4);
+ break;
+ case I_OR:
+ PUT(0x8001 | (node->instr.args[0].val << 8)
+ | node->instr.args[1].val << 4);
+ break;
+ case I_RET:
+ PUT(0x00EE);
+ break;
+ case I_RND:
+ PUT(0xC000 | (node->instr.args[0].val << 8)
+ | node->instr.args[1].val);
+ break;
+ case I_RSTR:
+ PUT(0xF065 | (node->instr.args[0].val << 8));
+ break;
+ case I_SE_VX_B:
+ PUT(0x3000 | (node->instr.args[0].val << 8)
+ | node->instr.args[1].val);
+ break;
+ case I_SE_VX_VY:
+ PUT(0x5000 | (node->instr.args[0].val << 8)
+ | node->instr.args[1].val << 4);
+ break;
+ case I_SHL:
+ PUT(0x800E | (node->instr.args[0].val << 8));
+ break;
+ case I_SHR:
+ PUT(0x8006 | (node->instr.args[0].val << 8));
+ break;
+ case I_SKNP:
+ PUT(0xE0A1 | (node->instr.args[0].val << 8));
+ break;
+ case I_SKP:
+ PUT(0xE09E | (node->instr.args[0].val << 8));
+ break;
+ case I_SNE_VX_B:
+ PUT(0x4000 | (node->instr.args[0].val << 8)
+ | node->instr.args[1].val);
+ break;
+ case I_SNE_VX_VY:
+ PUT(0x9000 | (node->instr.args[0].val << 8)
+ | node->instr.args[1].val << 4);
+ break;
+ case I_STOR:
+ PUT(0xF055 | (node->instr.args[0].val << 8));
+ break;
+ case I_SUB:
+ PUT(0x8005 | (node->instr.args[0].val << 8)
+ | node->instr.args[1].val << 4);
+ break;
+ case I_SUBN:
+ PUT(0x8007 | (node->instr.args[0].val << 8)
+ | node->instr.args[1].val << 4);
+ break;
+ case I_SYS:
+ PUT(getaddr(node->instr.args[0]));
+ break;
+ case I_XOR:
+ PUT(0x8003 | (node->instr.args[0].val << 8)
+ | node->instr.args[1].val << 4);
+ break;
+ default:
+ unreachable();
+ }
+ }
+
locals.len = 0;
+
+#undef PUT
}
diff --git a/src/c8asm/parser.c b/src/c8asm/parser.c
index b746bea..03d190d 100644
--- a/src/c8asm/parser.c
+++ b/src/c8asm/parser.c
@@ -383,10 +383,10 @@ parseop_jp(void)
if (op.kind == T_IDENT)
rt = regtype(op.sv);
if (op.kind == T_NUMBER || (op.kind == T_IDENT && rt == RT_NONE)) {
- ins.kind = I_JP_ADDR;
+ ins.kind = I_JP_A;
ins.args[ins.len++] = parseaddr(op);
} else if (op.kind == T_IDENT) {
- ins.kind = I_JP_V0_ADDR;
+ ins.kind = I_JP_V0_A;
if (op.sv.len != 2 || !memeq(op.sv.p, "v0", 2)) {
die_with_off(op.sv.p, E_EXPECTED, "v0-register or address",
tokrepr(op.kind), U8_PRI_ARGS(op.sv));
@@ -447,7 +447,7 @@ parseop_ld(void)
break;
case T_NUMBER:
- ins.kind = I_LD_VX_BYTE;
+ ins.kind = I_LD_VX_B;
ins.args[ins.len++].val = parsenum(op, NS_BYTE);
break;
default:
diff --git a/src/c8asm/parser.h b/src/c8asm/parser.h
index 392b003..a8cc03c 100644
--- a/src/c8asm/parser.h
+++ b/src/c8asm/parser.h
@@ -19,12 +19,12 @@ typedef enum {
I_DB,
I_DRW,
I_HEX,
- I_JP_ADDR,
- I_JP_V0_ADDR,
+ I_JP_A,
+ I_JP_V0_A,
I_LD_DT,
I_LD_I,
I_LD_ST,
- I_LD_VX_BYTE,
+ I_LD_VX_B,
I_LD_VX_DT,
I_LD_VX_K,
I_LD_VX_VY,