aboutsummaryrefslogtreecommitdiff
path: root/tasm/src
diff options
context:
space:
mode:
Diffstat (limited to 'tasm/src')
-rw-r--r--tasm/src/assembler.c393
-rw-r--r--tasm/src/bits.c75
-rw-r--r--tasm/src/lexer.l237
-rw-r--r--tasm/src/main.c68
-rw-r--r--tasm/src/parser.y532
-rw-r--r--tasm/src/source.mk8
6 files changed, 1313 insertions, 0 deletions
diff --git a/tasm/src/assembler.c b/tasm/src/assembler.c
new file mode 100644
index 0000000..c0c05be
--- /dev/null
+++ b/tasm/src/assembler.c
@@ -0,0 +1,393 @@
+#include <tasm/assembler.h>
+#include <tasm/parser.h>
+#include <tasm/bits.h>
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdio.h>
+#include <errno.h>
+
+struct asm_ctx {
+ size_t size;
+ size_t idx;
+ tri_t *buf;
+};
+
+static void emit(struct asm_ctx *ctx, tri_t i)
+{
+ if (ctx->size == ctx->idx) {
+ ctx->size *= 2;
+ ctx->buf = realloc(ctx->buf, ctx->size * sizeof(tri_t));
+ }
+
+ ctx->buf[ctx->idx++] = i;
+}
+
+static bool check_imm(tri_t imm, size_t n)
+{
+ /* check that we can fit imm into n trits */
+ return tri_mask(imm, n) == imm;
+}
+
+tri_t parse_imm(const char *imm)
+{
+ size_t len = strlen(imm);
+ if (len < 2) {
+ /* must be base 10 */
+ char *end = NULL;
+ int64_t val = strtoll(imm, &end, 10);
+ assert(imm + len == end);
+ return tri_from(val);
+ }
+
+ if (strncmp(imm, "0t", 2) == 0) {
+ /* must be raw trinary */
+ return tri_parse_default(imm, len);
+ }
+
+ if (strncmp(imm, "0h", 2) == 0) {
+ /* must be heptavintimal */
+ fprintf(stderr, "heptavintimal not yet implemented\n");
+ abort();
+ return 0;
+ }
+
+ /* try to let strtoll figure out */
+ char *end = NULL;
+ int64_t val = strtoll(imm, &end, 0);
+ if (imm + len == end)
+ return tri_from(val);
+
+
+ fprintf(stderr, "unrecognised immediate: %s\n", imm);
+ abort();
+ return 0;
+}
+
+void emit_r(struct asm_ctx *ctx, tri_t opcode,
+ enum gpr_num rd, tri_t fn0, enum gpr_num rs1,
+ enum gpr_num rs2, tri_t fn5)
+{
+ tri_t xrd = gpr_tri(rd );
+ tri_t xrs1 = gpr_tri(rs1);
+ tri_t xrs2 = gpr_tri(rs2);
+ tri_t i = build_r(opcode, xrd, fn0, xrs1, xrs2, fn5);
+ emit(ctx, i);
+}
+
+void emit_i(struct asm_ctx *ctx, tri_t opcode,
+ enum gpr_num rd, tri_t fn0, enum gpr_num rs1, tri_t imm9)
+{
+ tri_t xrd = gpr_tri(rd );
+ tri_t xrs1 = gpr_tri(rs1);
+ if (!check_imm(imm9, 9)) {
+ fprintf(stderr, "immediate %lx doesn't fit into 9 trits\n", imm9);
+ abort();
+ return;
+ }
+ tri_t i = build_i(opcode, xrd, fn0, xrs1, imm9);
+ emit(ctx, i);
+}
+
+void emit_s(struct asm_ctx *ctx, tri_t opcode,
+ tri_t fn0, enum gpr_num rs1, enum gpr_num rs2, tri_t imm9)
+{
+ tri_t xrs1 = gpr_tri(rs1);
+ tri_t xrs2 = gpr_tri(rs2);
+ if (!check_imm(imm9, 9)) {
+ fprintf(stderr, "immediate %lx doesn't fit into 9 trits\n", imm9);
+ abort();
+ return;
+ }
+ tri_t imm4 = tri_mask(imm9, 4);
+ tri_t imm5 = tri_mask(tri_sr(imm9, 4), 5);
+
+ tri_t i = build_s(opcode, imm4, fn0, xrs1, xrs2, imm5);
+ emit(ctx, i);
+}
+
+void emit_u(struct asm_ctx *ctx, tri_t opcode,
+ enum gpr_num rd, tri_t imm18)
+{
+ tri_t xrd = gpr_tri(rd);
+ if (!check_imm(imm18, 18)) {
+ fprintf(stderr, "immediate %lx does not fit into 18 trits\n", imm18);
+ abort();
+ return;
+ }
+
+ tri_t i = build_u(opcode, xrd, imm18);
+ emit(ctx, i);
+}
+
+void emit_d(struct asm_ctx *ctx, tri_t opcode,
+ enum gpr_num rd, enum gpr_num rs1, enum gpr_num rs2, tri_t imm10)
+{
+ tri_t xrd = gpr_tri(rd );
+ tri_t xrs1 = gpr_tri(rs1);
+ tri_t xrs2 = gpr_tri(rs2);
+
+ if (!check_imm(imm10, 10)) {
+ fprintf(stderr, "immediate %lx does not fit into 10 trits\n", imm10);
+ abort();
+ return;
+ }
+
+ tri_t imm0 = tri_mask(imm10, 5);
+ tri_t imm1 = tri_mask(tri_sr(imm10, 5), 5);
+
+ tri_t i = build_r(opcode, xrd, imm0, xrs1, xrs2, imm1);
+ emit(ctx, i);
+}
+
+static char *tasm_basename(const char *file)
+{
+ size_t l = strlen(file);
+ size_t n = l - 1;
+ while (--n) {
+ if (file[n] == '/')
+ break;
+ }
+
+ if (n == 0)
+ return strdup(file);
+
+ return strndup(file + n + 1, l - n);
+}
+
+static char *tasm_dirname(const char *file)
+{
+ size_t l = strlen(file);
+ size_t n = l - 1;
+ while (--n) {
+ if (file[n] == '/')
+ break;
+ }
+
+ return strndup(file, n);
+}
+
+static char *tasm_cwdname()
+{
+ size_t size;
+ long path_max = pathconf(".", _PC_PATH_MAX);
+ if (path_max == -1)
+ size = 1024;
+ else
+ size = (size_t)path_max;
+
+ char *buf = malloc(size);
+ if (!buf)
+ return NULL;
+
+ if (!getcwd(buf, size)) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ free(buf);
+ return NULL;
+ }
+
+ return buf;
+}
+
+static char *read_file(const char *file, FILE *f)
+{
+ fseek(f, 0, SEEK_END);
+ /** @todo check how well standardized this actually is */
+ long s = ftell(f);
+ if (s == LONG_MAX) {
+ fprintf(stderr, "%s might be a directory", file);
+ return NULL;
+ }
+
+ fseek(f, 0, SEEK_SET);
+
+ char *buf = malloc(s + 1);
+ if (!buf)
+ return NULL;
+
+ fread(buf, s + 1, 1, f);
+ /* remember terminating null */
+ buf[s] = 0;
+ return buf;
+}
+
+static int process(struct asm_ctx *ctx, const char *file)
+{
+ FILE *f = fopen(file, "rb");
+ if (!f) {
+ fprintf(stderr, "failed opening %s: %s\n", file, strerror(errno));
+ return -1;
+ }
+
+ const char *buf = read_file(file, f);
+ fclose(f);
+
+ if (!buf)
+ return -1;
+
+ struct parser *p = create_parser();
+ parse(p, ctx, file, buf);
+ bool failed = p->failed;
+ destroy_parser(p);
+
+ free((void *)buf);
+
+ if (failed)
+ return -1;
+
+ return 0;
+}
+
+/** @todo eventually take context */
+int process_file(struct asm_ctx *ctx, const char *file)
+{
+ const char *base = tasm_basename(file);
+ const char *dir = tasm_dirname(file);
+
+ const char *cwd = tasm_cwdname();
+
+ chdir(dir);
+ int res = process(ctx, base);
+ chdir(cwd);
+
+ free((void *)base);
+ free((void *)dir);
+ free((void *)cwd);
+
+ return res;
+}
+
+int assemble(const char *outfile, const char *infile)
+{
+ /** @todo cleanup */
+ struct asm_ctx ctx = {.size = 1, .buf = malloc(sizeof(tri_t)), .idx = 0};
+ int ret = process_file(&ctx, infile);
+ if (ret)
+ return ret;
+
+ FILE *f = fopen(outfile, "wb");
+ if (!f)
+ return -1;
+
+ for (size_t i = 0; i < ctx.idx; ++i) {
+ tri_t t = ctx.buf[i];
+ /* output trytewise LE */
+ uint32_t t0 = tri_mask(t, 9);
+ uint32_t t1 = tri_mask(tri_sr(t, 9), 9);
+ uint32_t t2 = tri_mask(tri_sr(t, 18), 9);
+
+ size_t c0 = fwrite(&t0, sizeof(t0), 1, f);
+ size_t c1 = fwrite(&t1, sizeof(t1), 1, f);
+ size_t c2 = fwrite(&t2, sizeof(t2), 1, f);
+
+ /* better error handling would be nice */
+ assert(c0);
+ assert(c1);
+ assert(c2);
+ }
+
+ fclose(f);
+ free(ctx.buf);
+}
+
+void check_shift(tri_t shmt)
+{
+ int64_t v = tri_from(shmt);
+ assert(v < 27);
+}
+
+tri_t check_nop(const char *nop)
+{
+ tri_t r = 0;
+ size_t count = 0;
+ size_t len = strlen(nop);
+ for (size_t i = 0; i < len; ++i) {
+ switch (nop[i]) {
+ case 'o':
+ case 'O':
+ case '0': tri_set_trit(r, i, 0); break;
+
+ case 'n':
+ case 'N':
+ case 'i': tri_set_trit(r, i, -1); break;
+
+ case 'p':
+ case 'P':
+ case '1': tri_set_trit(r, i, 1); break;
+
+ case ' ': continue;
+ default:
+ fprintf(stderr, "invalid character in unop format: %c\n", nop[i]);
+ abort();
+ }
+
+ count++;
+ }
+
+ assert(count == 3);
+ return r;
+}
+
+tri_t check_nop3(const char *nop)
+{
+ tri_t r = 0;
+ size_t count = 0;
+ size_t len = strlen(nop);
+ for (size_t i = 0; i < len; ++i) {
+ switch (nop[i]) {
+ case 'o':
+ case 'O':
+ case '0': tri_set_trit(r, i, 0); break;
+
+ case 'n':
+ case 'N':
+ case 'i': tri_set_trit(r, i, -1); break;
+
+ case 'p':
+ case 'P':
+ case '1': tri_set_trit(r, i, 1); break;
+
+ case ' ': continue;
+ default:
+ fprintf(stderr, "invalid character in unop format: %c\n", nop[i]);
+ abort();
+ }
+
+ count++;
+ }
+
+ assert(count == 9);
+ return r;
+}
+
+tri_t check_csr(const char *csr)
+{
+ /** @todo only mpower supported for now, will have to work on this */
+ assert(strcmp(csr, "mpower"));
+ return 0;
+}
+
+tri_t check_width(const char *width)
+{
+ if (strncmp(width, "w", 1) == 0)
+ return WIDTH_W;
+
+ if (strncmp(width, "t", 1) == 0)
+ return WIDTH_T;
+
+ fprintf(stderr, "illegal width specifier: %s\n", width);
+ abort();
+ return 0;
+}
+
+void emit_reloc(struct asm_ctx *ctx, enum asm_reloc reloc, const char *name)
+{
+ /** @todo implement */
+}
+
+void emit_label(struct asm_ctx *ctx, const char *name)
+{
+ /** @todo implement */
+}
diff --git a/tasm/src/bits.c b/tasm/src/bits.c
new file mode 100644
index 0000000..6397e2f
--- /dev/null
+++ b/tasm/src/bits.c
@@ -0,0 +1,75 @@
+#include <tasm/bits.h>
+
+tri_t parse_opcode(tri_t i)
+{
+ /* opcode is lowest five trits */
+ return tri_mask(i, 5);
+}
+
+tri_t build_r(tri_t opcode, tri_t rd, tri_t fn0, tri_t rs1, tri_t rs2, tri_t fn5)
+{
+ return opcode
+ | tri_sl(rd , 5)
+ | tri_sl(fn0, 9)
+ | tri_sl(rs1, 14)
+ | tri_sl(rs2, 18)
+ | tri_sl(fn5, 22);
+}
+
+void parse_r(tri_t i, tri_t *rd, tri_t *fn0, tri_t *rs1, tri_t *rs2, tri_t *fn5)
+{
+ *rd = tri_mask(tri_sr(i, 5), 4);
+ *fn0 = tri_mask(tri_sr(i, 9), 5);
+ *rs1 = tri_mask(tri_sr(i, 14), 4);
+ *rs2 = tri_mask(tri_sr(i, 18), 4);
+ *fn5 = tri_mask(tri_sr(i, 22), 5);
+}
+
+tri_t build_i(tri_t opcode, tri_t rd, tri_t fn0, tri_t rs1, tri_t imm9)
+{
+ return opcode
+ | tri_sl(rd , 5)
+ | tri_sl(fn0 , 9)
+ | tri_sl(rs1 , 14)
+ | tri_sl(imm9, 18);
+}
+
+void parse_i(tri_t i, tri_t *rd, tri_t *fn0, tri_t *rs1, tri_t *imm9)
+{
+ *rd = tri_mask(tri_sl(i, 5), 4);
+ *fn0 = tri_mask(tri_sl(i, 9), 5);
+ *rs1 = tri_mask(tri_sl(i, 14), 4);
+ *imm9 = tri_mask(tri_sl(i, 18), 9);
+}
+
+tri_t build_s(tri_t opcode, tri_t imm4, tri_t fn0, tri_t rs1, tri_t rs2, tri_t imm5)
+{
+ return opcode
+ | tri_sl(imm4, 5)
+ | tri_sl(fn0 , 9)
+ | tri_sl(rs1 , 14)
+ | tri_sl(rs2 , 18)
+ | tri_sl(imm5, 22);
+}
+
+void parse_s(tri_t i, tri_t *imm4, tri_t *fn0, tri_t *rs1, tri_t *rs2, tri_t *imm5)
+{
+ *imm4 = tri_mask(tri_sl(i, 5), 4);
+ *fn0 = tri_mask(tri_sl(i, 9), 5);
+ *rs1 = tri_mask(tri_sl(i, 14), 4);
+ *rs2 = tri_mask(tri_sl(i, 18), 4);
+ *imm5 = tri_mask(tri_sl(i, 22), 5);
+}
+
+tri_t build_u(tri_t opcode, tri_t rd, tri_t imm18)
+{
+ return opcode
+ | tri_sl(rd , 5)
+ | tri_sl(imm18, 9);
+}
+
+void parse_u(tri_t i, tri_t *rd, tri_t *imm18)
+{
+ *rd = tri_mask(tri_sl(i, 5), 4);
+ *imm18 = tri_mask(tri_sl(i, 9), 18);
+}
diff --git a/tasm/src/lexer.l b/tasm/src/lexer.l
new file mode 100644
index 0000000..01a4bee
--- /dev/null
+++ b/tasm/src/lexer.l
@@ -0,0 +1,237 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+%option reentrant noyywrap nounput noinput nodefault
+%{
+#define FROM_LEXER
+#include <tasm/parser.h>
+
+static void update_yylloc(struct parser *parser, YYLTYPE *lloc, const char *text)
+{
+ (void)parser;
+
+ lloc->first_line = lloc->last_line;
+ lloc->first_column = lloc->last_column;
+
+ for (size_t i = 0; text[i] != 0; ++i) {
+ if (text[i] == '\n') {
+ lloc->last_line++;
+ /* flex uses 1 based indexing */
+ lloc->last_column = 1;
+ } else {
+ lloc->last_column++;
+ }
+ }
+}
+
+#define YY_USER_ACTION update_yylloc(parser, yylloc, yytext);
+%}
+
+%x SC_COMMENT
+
+id [_a-zA-Z][_a-zA-Z0-9]*
+label {id}[[:space:]]*:
+
+dec -?[0-9]+
+tri 0t[01i]+
+hept 0h[0-9A-HKMNPRTVXZa-hkmnprtvxz]+
+imm {tri}|{hept}|{dec}
+
+str \"(\\.|[^"\\])*\"
+
+%%
+
+{label} {
+ /* strip trailing ':' */
+ char *s = yytext + strlen(yytext);
+ s[-1] = '\0';
+
+ yylval->str = yytext;
+ return label;
+}
+
+{imm} {
+ yylval->tri = parse_imm(yytext);
+ return imm;
+}
+
+{str} {
+ /* strip leading and trailing " */
+ char *s = yytext + strlen(yytext);
+ s[-1] = '\0';
+
+ yylval->str = yytext + 1;
+ return str;
+}
+
+".set" {return set;}
+".include" {return include;}
+
+"x0" {return x0;}
+"x1" {return x1;}
+"x2" {return x2;}
+"x3" {return x3;}
+"x4" {return x4;}
+"x5" {return x5;}
+"x6" {return x6;}
+"x7" {return x7;}
+"x8" {return x8;}
+"x9" {return x9;}
+"x10" {return x10;}
+"x11" {return x11;}
+"x12" {return x12;}
+"x13" {return x13;}
+"x14" {return x14;}
+"x15" {return x15;}
+"x16" {return x16;}
+"x17" {return x17;}
+"x18" {return x18;}
+"x19" {return x19;}
+"x20" {return x20;}
+"x21" {return x21;}
+"x22" {return x22;}
+"x23" {return x23;}
+"x24" {return x24;}
+"x25" {return x25;}
+"x26" {return x26;}
+"x27" {return x27;}
+"x28" {return x28;}
+"x29" {return x29;}
+"x30" {return x30;}
+"x31" {return x31;}
+"x32" {return x32;}
+"x33" {return x33;}
+"x34" {return x34;}
+"x35" {return x35;}
+"x36" {return x36;}
+"x37" {return x37;}
+"x38" {return x38;}
+"x39" {return x39;}
+"x40" {return x40;}
+"x41" {return x41;}
+"x42" {return x42;}
+"x43" {return x43;}
+"x44" {return x44;}
+"x45" {return x45;}
+"x46" {return x46;}
+"x47" {return x47;}
+"x48" {return x48;}
+"x49" {return x49;}
+"x50" {return x50;}
+"x51" {return x51;}
+"x52" {return x52;}
+"x53" {return x53;}
+"x54" {return x54;}
+"x55" {return x55;}
+"x56" {return x56;}
+"x57" {return x57;}
+"x58" {return x58;}
+"x59" {return x59;}
+"x60" {return x60;}
+"x61" {return x61;}
+"x62" {return x62;}
+"x63" {return x63;}
+"x64" {return x64;}
+"x65" {return x65;}
+"x66" {return x66;}
+"x67" {return x67;}
+"x68" {return x68;}
+"x69" {return x69;}
+"x70" {return x70;}
+"x71" {return x71;}
+"x72" {return x72;}
+"x73" {return x73;}
+"x74" {return x74;}
+"x75" {return x75;}
+"x76" {return x76;}
+"x77" {return x77;}
+"x78" {return x78;}
+"x79" {return x79;}
+"x80" {return x80;}
+
+"addi" {return addi; /* i */}
+"slti" {return slti;}
+"sgei" {return sgei;}
+"seqi" {return seqi;}
+"snei" {return snei;}
+"unop" {return unop;}
+"diop" {return diop;}
+"slli" {return slli;}
+"srli" {return srli;}
+"sll" {return sll;}
+"srl" {return srl;}
+"add" {return add;}
+"sub" {return sub;}
+"slt" {return slt;}
+"sge" {return sge;}
+"seq" {return seq;}
+"sne" {return sne;}
+"lui" {return lui;}
+"auipc" {return auipc;}
+"jal" {return jal;}
+"jalr" {return jalr;}
+"beq" {return beq;}
+"bne" {return bne;}
+"blt" {return blt;}
+"bge" {return bge;}
+"ld" {return ld;}
+"st" {return st;}
+"ecall" {return ecall;}
+"ebreak" {return ebreak;}
+"pcall" {return pcall;}
+"fence" {return fence;}
+"nop" {return nop; /* meta */}
+"li" {return li;}
+"la" {return la;}
+
+"mul" {return mul; /* m */}
+"div" {return diV;}
+"rem" {return rem;}
+
+"stt" {return stt; /* a */}
+"cst" {return cst;}
+"ent" {return ent;}
+"cat" {return cat;}
+
+"csrrw" {return csrrw; /* Zcsr */}
+"csrrs" {return csrrs;}
+"csrrc" {return csrrc;}
+
+"//".* {/* skip line comments */}
+
+"/*" {BEGIN(SC_COMMENT);}
+<SC_COMMENT>{
+ "/*" {++parser->comment_nesting;}
+ "*"+"/" {
+ if (parser->comment_nesting)
+ --parser->comment_nesting;
+ else
+ BEGIN(INITIAL);
+ }
+
+ "*"+ {}
+ [^/*\n]+ {}
+ [/] {}
+ \n {}
+}
+
+{id} {
+ yylval->str = yytext;
+ return id;
+}
+
+"(" {return LPAREN;}
+")" {return RPAREN;}
+"," {return COMMA;}
+
+[[:space:]]+ {/* skip whitespace */}
+
+. {
+ struct src_issue issue;
+ issue.loc = to_src_loc(yylloc);
+ issue.fctx.fbuf = parser->buf;
+ issue.fctx.fname = parser->fname;
+ src_issue(issue, "unexpected token: %s", yytext);
+ parser->failed = true;
+}
+%%
diff --git a/tasm/src/main.c b/tasm/src/main.c
new file mode 100644
index 0000000..bb6a1ac
--- /dev/null
+++ b/tasm/src/main.c
@@ -0,0 +1,68 @@
+#include <tasm/assembler.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+
+static const char *cmdline_usage =
+"tasm trinary assembler usage:\n"
+" tasm [-I <dir>...] -o outfile infile\n"
+" -h Show usage (this)\n"
+" -I <dir> Add directory to include path\n"
+" -o <outfile> File to output to\n"
+" infile Top file to assemble\n"
+;
+
+static void usage()
+{
+ fprintf(stderr, cmdline_usage);
+}
+
+int main(int argc, char *argv[])
+{
+ const char *outfile = NULL;
+ int opt;
+ while ((opt = getopt(argc, argv, "hI:o:")) != -1) {
+ switch (opt) {
+ case 'I':
+ fprintf(stderr, "include paths not yet implemented\n");
+ break;
+
+ case 'o':
+ outfile = optarg;
+ break;
+
+ case 'h':
+ usage();
+ exit(EXIT_SUCCESS);
+ break;
+
+ default:
+ usage();
+ exit(EXIT_FAILURE);
+ break;
+ }
+ }
+
+ if (!outfile) {
+ fprintf(stderr, "no output file\n");
+ usage();
+ exit(EXIT_FAILURE);
+ }
+
+ if (optind >= argc) {
+ fprintf(stderr, "no input files\n");
+ usage();
+ exit(EXIT_FAILURE);
+ }
+
+ if (optind != argc - 1) {
+ fprintf(stderr, "too many input files\n");
+ usage();
+ exit(EXIT_FAILURE);
+ }
+
+ if (assemble(outfile, argv[optind]))
+ exit(EXIT_FAILURE);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tasm/src/parser.y b/tasm/src/parser.y
new file mode 100644
index 0000000..ef8d3df
--- /dev/null
+++ b/tasm/src/parser.y
@@ -0,0 +1,532 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+%{
+
+/* get access to fileno to avoid warnings with flex */
+#define _POSIX_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <tasm/assembler.h>
+#include <tasm/parser.h>
+#include <tasm/bits.h>
+#include <tasm/regs.h>
+#include <tri.h>
+
+%}
+
+%locations
+
+%define parse.trace
+%define parse.error verbose
+%define api.pure full
+%define lr.type ielr
+
+%lex-param {void *scanner} {struct parser *parser}
+%parse-param {void *scanner} {struct parser *parser} {struct asm_ctx *ctx}
+
+%union {
+ const char *str;
+ enum gpr_num gpr;
+ tri_t tri;
+};
+
+%token <tri> imm
+%token <str> label
+%token <str> str
+%token <str> id
+
+%token COMMA ","
+%token LPAREN "("
+%token RPAREN ")"
+
+%token set
+%token include
+%nterm <gpr> gpr;
+%nterm <str> width addr csr
+
+/* regs */
+%token x0 x1 x2 x3 x4 x5 x6 x7 x8 x9
+%token x10 x11 x12 x13 x14 x15 x16 x17 x18 x19
+%token x20 x21 x22 x23 x24 x25 x26 x27 x28 x29
+%token x30 x31 x32 x33 x34 x35 x36 x37 x38 x39
+%token x40 x41 x42 x43 x44 x45 x46 x47 x48 x49
+%token x50 x51 x52 x53 x54 x55 x56 x57 x58 x59
+%token x60 x61 x62 x63 x64 x65 x66 x67 x68 x69
+%token x70 x71 x72 x73 x74 x75 x76 x77 x78 x79
+%token x80
+
+/* i */
+%token addi slti sgei seqi snei
+%token unop diop
+%token slli srli sll srl
+%token add sub
+%token slt sge seq sne
+%token lui auipc
+%token jal jalr
+%token beq bne blt bge
+%token ld st
+%token ecall ebreak pcall
+%token fence
+/* meta */
+%token li la nop
+
+/* m (avoid name clash with stdlib div) */
+%token mul diV rem
+
+/* a */
+%token stt cst ent cat
+
+/* Zcsr */
+%token csrrw csrrs csrrc
+
+%{
+
+struct file_ctx {
+ const char *fname;
+ const char *fbuf;
+};
+
+struct src_loc {
+ int first_line;
+ int last_line;
+ int first_col;
+ int last_col;
+};
+
+struct src_issue {
+ struct src_loc loc;
+ struct file_ctx fctx;
+};
+
+/** Modifies the signature of yylex to fit our parser better. */
+#define YY_DECL int yylex(YYSTYPE *yylval, YYLTYPE *yylloc, \
+ void *yyscanner, struct parser *parser)
+
+/**
+ * Declare yylex.
+ *
+ * @param yylval Bison current value.
+ * @param yylloc Bison location info.
+ * @param yyscanner Flex scanner.
+ * @param parser Current parser state.
+ * @return \c 0 when succesful, \c 1 otherwise.
+ * More info on yylex() can be found in the flex manual.
+ */
+YY_DECL;
+
+/**
+ * Convert bison location info to our own source location info.
+ *
+ * @param yylloc Bison location info.
+ * @return Internal location info.
+ */
+static struct src_loc to_src_loc(YYLTYPE *yylloc);
+
+/**
+ * Print parsing error.
+ * Automatically called by bison.
+ *
+ * @param yylloc Location of error.
+ * @param lexer Lexer.
+ * @param parser Parser state.
+ * @param msg Message to print.
+ */
+static void yyerror(YYLTYPE *yylloc, void *lexer,
+ struct parser *parser, struct asm_ctx *ctx, const char *msg);
+
+%}
+
+%start input;
+%%
+
+width
+ : id
+
+/* eventually we might allow jumping to immediate addresses, but for now require
+ * that a label always be used */
+addr
+ : id
+
+/* lexer makes sure the abi names are correct */
+gpr
+ : x0 {$$ = X0_NUM;}
+ | x1 {$$ = X1_NUM;}
+ | x2 {$$ = X2_NUM;}
+ | x3 {$$ = X3_NUM;}
+ | x4 {$$ = X4_NUM;}
+ | x5 {$$ = X5_NUM;}
+ | x6 {$$ = X6_NUM;}
+ | x7 {$$ = X7_NUM;}
+ | x8 {$$ = X8_NUM;}
+ | x9 {$$ = X9_NUM;}
+ | x10 {$$ = X10_NUM;}
+ | x11 {$$ = X11_NUM;}
+ | x12 {$$ = X12_NUM;}
+ | x13 {$$ = X13_NUM;}
+ | x14 {$$ = X14_NUM;}
+ | x15 {$$ = X15_NUM;}
+ | x16 {$$ = X16_NUM;}
+ | x17 {$$ = X17_NUM;}
+ | x18 {$$ = X18_NUM;}
+ | x19 {$$ = X19_NUM;}
+ | x20 {$$ = X20_NUM;}
+ | x21 {$$ = X21_NUM;}
+ | x22 {$$ = X22_NUM;}
+ | x23 {$$ = X23_NUM;}
+ | x24 {$$ = X24_NUM;}
+ | x25 {$$ = X25_NUM;}
+ | x26 {$$ = X26_NUM;}
+ | x27 {$$ = X27_NUM;}
+ | x28 {$$ = X28_NUM;}
+ | x29 {$$ = X29_NUM;}
+ | x30 {$$ = X30_NUM;}
+ | x31 {$$ = X31_NUM;}
+ | x32 {$$ = X32_NUM;}
+ | x33 {$$ = X33_NUM;}
+ | x34 {$$ = X34_NUM;}
+ | x35 {$$ = X35_NUM;}
+ | x36 {$$ = X36_NUM;}
+ | x37 {$$ = X37_NUM;}
+ | x38 {$$ = X38_NUM;}
+ | x39 {$$ = X39_NUM;}
+ | x40 {$$ = X40_NUM;}
+ | x41 {$$ = X41_NUM;}
+ | x42 {$$ = X42_NUM;}
+ | x43 {$$ = X43_NUM;}
+ | x44 {$$ = X44_NUM;}
+ | x45 {$$ = X45_NUM;}
+ | x46 {$$ = X46_NUM;}
+ | x47 {$$ = X47_NUM;}
+ | x48 {$$ = X48_NUM;}
+ | x49 {$$ = X49_NUM;}
+ | x50 {$$ = X50_NUM;}
+ | x51 {$$ = X51_NUM;}
+ | x52 {$$ = X52_NUM;}
+ | x53 {$$ = X53_NUM;}
+ | x54 {$$ = X54_NUM;}
+ | x55 {$$ = X55_NUM;}
+ | x56 {$$ = X56_NUM;}
+ | x57 {$$ = X57_NUM;}
+ | x58 {$$ = X58_NUM;}
+ | x59 {$$ = X59_NUM;}
+ | x60 {$$ = X60_NUM;}
+ | x61 {$$ = X61_NUM;}
+ | x62 {$$ = X62_NUM;}
+ | x63 {$$ = X63_NUM;}
+ | x64 {$$ = X64_NUM;}
+ | x65 {$$ = X65_NUM;}
+ | x66 {$$ = X66_NUM;}
+ | x67 {$$ = X67_NUM;}
+ | x68 {$$ = X68_NUM;}
+ | x69 {$$ = X69_NUM;}
+ | x70 {$$ = X70_NUM;}
+ | x71 {$$ = X71_NUM;}
+ | x72 {$$ = X72_NUM;}
+ | x73 {$$ = X73_NUM;}
+ | x74 {$$ = X74_NUM;}
+ | x75 {$$ = X75_NUM;}
+ | x76 {$$ = X76_NUM;}
+ | x77 {$$ = X77_NUM;}
+ | x78 {$$ = X78_NUM;}
+ | x79 {$$ = X79_NUM;}
+ | x80 {$$ = X80_NUM;}
+
+i
+ : addi gpr "," gpr "," imm
+ {emit_i(ctx, OPCODE_OP_IMM, $2, OP_IMM_ADDI, $4, $6);}
+
+ | slti gpr "," gpr "," imm
+ {emit_i(ctx, OPCODE_OP_IMM, $2, OP_IMM_SLTI, $4, $6);}
+
+ | sgei gpr "," gpr "," imm
+ {emit_i(ctx, OPCODE_OP_IMM, $2, OP_IMM_SGEI, $4, $6);}
+
+ | seqi gpr "," gpr "," imm
+ {emit_i(ctx, OPCODE_OP_IMM, $2, OP_IMM_SEQI, $4, $6);}
+
+ | snei gpr "," gpr "," imm
+ {emit_i(ctx, OPCODE_OP_IMM, $2, OP_IMM_SNEI, $4, $6);}
+
+ | slli gpr "," gpr "," imm
+ {check_shift($6); emit_i(ctx, OPCODE_OP_IMM, $2, OP_IMM_SLLI, $4, $6);}
+
+ | srli gpr "," gpr "," imm
+ {check_shift($6); emit_i(ctx, OPCODE_OP_IMM, $2, OP_IMM_SRLI, $4, $6);}
+
+ | add gpr "," gpr "," gpr
+ {emit_r(ctx, OPCODE_OP, $2, OP_ADD, $4, $6, 0);}
+
+ | sub gpr "," gpr "," gpr
+ {emit_r(ctx, OPCODE_OP, $2, OP_SUB, $4, $6, 0);}
+
+ | slt gpr "," gpr "," gpr
+ {emit_r(ctx, OPCODE_OP, $2, OP_SLT, $4, $6, 0);}
+
+ | sge gpr "," gpr "," gpr
+ {emit_r(ctx, OPCODE_OP, $2, OP_SGE, $4, $6, 0);}
+
+ | seq gpr "," gpr "," gpr
+ {emit_r(ctx, OPCODE_OP, $2, OP_SEQ, $4, $6, 0);}
+
+ | sne gpr "," gpr "," gpr
+ {emit_r(ctx, OPCODE_OP, $2, OP_SNE, $4, $6, 0);}
+
+ | sll gpr "," gpr "," gpr
+ {emit_r(ctx, OPCODE_OP, $2, OP_SLL, $4, $6, 0);}
+
+ | srl gpr "," gpr "," gpr
+ {emit_r(ctx, OPCODE_OP, $2, OP_SRL, $4, $6, 0);}
+
+ | lui gpr "," imm
+ {emit_u(ctx, OPCODE_LUI, $2, $4);}
+
+ | auipc gpr "," imm
+ {emit_u(ctx, OPCODE_AUIPC, $2, $4);}
+
+ | jal gpr "," addr
+ {emit_u(ctx, OPCODE_JAL, $2, 0);
+ emit_reloc(ctx, RELOC_J, $4);}
+
+ | jalr gpr "," gpr "," imm
+ {emit_i(ctx, OPCODE_JALR, $2, 0, $4, $6);}
+
+ | beq gpr "," gpr "," addr
+ {emit_s(ctx, OPCODE_BRANCH, BRANCH_BEQ, $2, $4, 0);
+ emit_reloc(ctx, RELOC_B, $6);}
+
+ | bne gpr "," gpr "," addr
+ {emit_s(ctx, OPCODE_BRANCH, BRANCH_BNE, $2, $4, 0);
+ emit_reloc(ctx, RELOC_B, $6);}
+
+ | blt gpr "," gpr "," addr
+ {emit_s(ctx, OPCODE_BRANCH, BRANCH_BLT, $2, $4, 0);
+ emit_reloc(ctx, RELOC_B, $6);}
+
+ | bge gpr "," gpr "," addr
+ {emit_s(ctx, OPCODE_BRANCH, BRANCH_BGE, $2, $4, 0);
+ emit_reloc(ctx, RELOC_B, $6);}
+
+ | ld width "," gpr "," imm "(" gpr ")"
+ {emit_i(ctx, OPCODE_LOAD, check_width($2), $4, $8, $6);}
+
+ | st width "," gpr "," imm "(" gpr ")"
+ {emit_s(ctx, OPCODE_STORE, check_width($2), $4, $8, $6);}
+
+ | ecall
+ {emit_i(ctx, OPCODE_SYSTEM, X0_NUM, SYSTEM_ECALL, X0_NUM, 0);}
+
+ | ebreak
+ {emit_i(ctx, OPCODE_SYSTEM, X0_NUM, SYSTEM_EBREAK, X0_NUM, 0);}
+
+ | pcall
+ {emit_i(ctx, OPCODE_SYSTEM, X0_NUM, SYSTEM_PCALL, X0_NUM, 0);}
+
+ | fence
+ {emit_i(ctx, OPCODE_MEM, X0_NUM, MEM_FENCE, X0_NUM, 0); /* still todo */}
+
+ | unop gpr "," gpr "," str
+ {emit_i(ctx, OPCODE_OP_IMM, $2, OP_IMM_UNOP, $4, check_nop($6));}
+
+ | diop gpr "," gpr "," gpr "," str
+ {emit_d(ctx, OPCODE_DIOP, $2, $4, $6, check_nop3($8));}
+
+ /* meta */
+ | nop
+ {emit_i(ctx, OPCODE_OP_IMM, X0_NUM, OP_IMM_ADDI, X0_NUM, 0);}
+
+ | la gpr "," addr
+ {emit_u(ctx, OPCODE_LUI, $2, 0);
+ /* important that the reloc is in the middle here */
+ emit_reloc(ctx, RELOC_LA, $4);
+ emit_i(ctx, OPCODE_OP_IMM, $2, OP_IMM_ADDI, $2, 0);}
+
+ | li gpr "," imm
+ {emit_u(ctx, OPCODE_LUI, $2, 0);
+ emit_i(ctx, OPCODE_OP_IMM, $2, OP_IMM_ADDI, $2, 0);}
+ /* stuff like call and ret TBD once I've come up with a proper register
+ * calling convention so we know which register to use as ra */
+
+m
+ : mul gpr "," gpr "," gpr
+ {emit_r(ctx, OPCODE_OP, $2, OP_MUL, $4, $6, 0);}
+
+ | diV gpr "," gpr "," gpr
+ {emit_r(ctx, OPCODE_OP, $2, OP_DIV, $4, $6, 0);}
+
+ | rem gpr "," gpr "," gpr
+ {emit_r(ctx, OPCODE_OP, $2, OP_DIV, $4, $6, 0);}
+
+a
+ : stt
+ {emit_i(ctx, OPCODE_MEM, X0_NUM, MEM_STT, X0_NUM, 0);}
+
+ | cst width "," gpr "," imm "(" gpr ")"
+ {emit_s(ctx, OPCODE_STORE, STORE_CST | check_width($2), $4, $8, $6);}
+
+ | ent gpr
+ {emit_i(ctx, OPCODE_MEM, $2, MEM_ENT, X0_NUM, 0);}
+
+ | cat
+ {emit_i(ctx, OPCODE_MEM, X0_NUM, MEM_CAT, X0_NUM, 0);}
+
+Zcsr
+ : csrrw csr "," gpr "," gpr
+ {emit_i(ctx, OPCODE_SYSTEM, $4, SYSTEM_CSRRW, $6, check_csr($2));}
+
+ | csrrs csr "," gpr "," gpr
+ {emit_i(ctx, OPCODE_SYSTEM, $4, SYSTEM_CSRRS, $6, check_csr($2));}
+
+ | csrrc csr "," gpr "," gpr
+ {emit_i(ctx, OPCODE_SYSTEM, $4, SYSTEM_CSRRC, $6, check_csr($2));}
+
+csr
+ : id
+
+statement
+ : i
+ | a
+ | m
+ | Zcsr
+
+directive
+ : set id imm
+ | include str {process_file(ctx, $2);}
+
+top
+ : label {emit_label(ctx, $1);}
+ | statement
+ | directive
+
+unit
+ : top
+ | top unit
+
+input
+ : unit
+ | /* empty */
+
+%%
+
+static const char *find_lineno(const char *buf, size_t no)
+{
+ if (no == 0 || no == 1)
+ return buf;
+
+ char c;
+ while ((c = *buf)) {
+ buf++;
+
+ if (c == '\n')
+ no--;
+
+ if (no == 1)
+ break;
+ }
+
+ return buf;
+}
+
+static void _issue(struct src_issue issue, const char *fmt, va_list args)
+{
+ /* get start and end of current line in buffer */
+ const char *line_start = find_lineno(issue.fctx.fbuf,
+ issue.loc.first_line);
+ const char *line_end = strchr(line_start, '\n');
+ if (!line_end)
+ line_end = strchr(line_start, 0);
+
+ const int line_len = line_end - line_start;
+
+ fprintf(stderr, "%s:%i:%i: ",
+ issue.fctx.fname,
+ issue.loc.first_line,
+ issue.loc.first_col);
+
+ vfprintf(stderr, fmt, args);
+ fputc('\n', stderr);
+
+ int lineno_len = snprintf(NULL, 0, "%i", issue.loc.first_line);
+ fputc(' ', stderr);
+ fprintf(stderr, "%i | ", issue.loc.first_line);
+
+ fprintf(stderr, "%.*s\n", line_len, line_start);
+
+ for (int i = 0; i < lineno_len + 2; ++i)
+ fputc(' ', stderr);
+
+ fprintf(stderr, "| ");
+
+ for (int i = 0; i < issue.loc.first_col - 1; ++i)
+ fputc(line_start[i] == '\t' ? '\t' : ' ', stderr);
+
+ for (int i = issue.loc.first_col; i < issue.loc.last_col; ++i) {
+ if (i == issue.loc.first_col)
+ fputc('^', stderr);
+ else
+ fputc('~', stderr);
+ }
+
+ fputc('\n', stderr);
+}
+
+void src_issue(struct src_issue issue, const char *err_msg, ...)
+{
+ va_list args;
+ va_start(args, err_msg);
+ _issue(issue, err_msg, args);
+ va_end(args);
+}
+#include "gen_lexer.inc"
+
+static struct src_loc to_src_loc(YYLTYPE *yylloc)
+{
+ struct src_loc loc;
+ loc.first_line = yylloc->first_line;
+ loc.last_line = yylloc->last_line;
+ loc.first_col = yylloc->first_column;
+ loc.last_col = yylloc->last_column;
+ return loc;
+}
+
+static void yyerror(YYLTYPE *yylloc, void *lexer,
+ struct parser *parser, struct asm_ctx *ctx, const char *msg)
+{
+ (void)lexer;
+ (void)ctx;
+
+ struct src_issue issue;
+ issue.loc = to_src_loc(yylloc);
+ issue.fctx.fbuf = parser->buf;
+ issue.fctx.fname = parser->fname;
+ src_issue(issue, msg);
+}
+
+struct parser *create_parser()
+{
+ return calloc(1, sizeof(struct parser));
+}
+
+void destroy_parser(struct parser *p)
+{
+ yylex_destroy(p->lexer);
+ free(p);
+}
+
+void parse(struct parser *p, struct asm_ctx *ctx, const char *fname, const char *buf)
+{
+ p->fname = fname;
+ p->buf = buf;
+
+ p->comment_nesting = 0;
+
+ p->failed = false;
+
+ yylex_init(&p->lexer);
+ yy_scan_string(buf, p->lexer);
+ yyparse(p->lexer, p, ctx);
+}
diff --git a/tasm/src/source.mk b/tasm/src/source.mk
new file mode 100644
index 0000000..5052ba3
--- /dev/null
+++ b/tasm/src/source.mk
@@ -0,0 +1,8 @@
+SOURCES != echo src/*.c
+TASM_SOURCES += $(SOURCES) gen/gen_parser.c
+
+gen/gen_parser.c: src/parser.y gen/gen_lexer.inc
+ bison -Wcounterexamples -o gen/gen_parser.c src/parser.y
+
+gen/gen_lexer.inc: src/lexer.l
+ flex -o gen/gen_lexer.inc src/lexer.l