aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKimplul <kimi.h.kuparinen@gmail.com>2024-06-23 23:31:46 +0300
committerKimplul <kimi.h.kuparinen@gmail.com>2024-06-23 23:38:49 +0300
commit5a4da8bac457f0e70e690a572eba9cf754e69a37 (patch)
tree58e48f1b12722eadbc1b2587b690b30afac54140
downloadejit-5a4da8bac457f0e70e690a572eba9cf754e69a37.tar.gz
ejit-5a4da8bac457f0e70e690a572eba9cf754e69a37.zip
initial interpeter testing
-rw-r--r--.gitignore6
-rw-r--r--.gitmodules3
-rw-r--r--Makefile60
m---------deps/lightening0
-rw-r--r--examples/main.c32
-rw-r--r--include/ejit/ejit.h256
-rwxr-xr-xscripts/gen-deps37
-rwxr-xr-xscripts/license16
-rw-r--r--scripts/makefile71
-rwxr-xr-xscripts/warn-undocumented7
-rw-r--r--src/common.h126
-rw-r--r--src/ejit.c190
-rw-r--r--src/interp.c190
-rw-r--r--src/source.mk2
-rw-r--r--src/vec.h95
15 files changed, 1091 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a1899e9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+deps.mk
+docs/output
+build
+ejit.o
+examples/exec
+examples/*.d
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..a514045
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "deps/lightening"]
+ path = deps/lightening
+ url = https://gitlab.com/Kimplul/lightening.git
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..839fd5f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,60 @@
+.PHONY: all
+all: setup
+ $(MAKE) -f scripts/makefile
+
+.PHONY: examples
+examples: setup
+ $(MAKE) -f scripts/makefile examples
+
+# this kicks all unrecognised targets to the client script.
+# note that trying to compile individual files, e.g.
+#
+# make kernel.elf
+#
+# will not work, you would need
+#
+# make -f scripts/makefile kernel.elf
+#
+# instead
+.DEFAULT: setup
+ $(MAKE) -f scripts/makefile $<
+
+.PHONY:
+setup:
+ @echo -n > deps.mk
+ @./scripts/gen-deps -p EJIT -c COMPILE_EJIT -b ejit "$(EJIT_SOURCES)"
+
+CLEANUP := build deps.mk ejit.o examples/exec examples/*.d
+CLEANUP_CMD :=
+EJIT_SOURCES :=
+
+include src/source.mk
+
+.PHONY: format
+format:
+ find src include -iname '*.[ch]' |\
+ xargs uncrustify -c uncrustify.conf --no-backup -F -
+
+.PHONY: license
+license:
+ find src include -iname '*.[ch]' |\
+ xargs ./scripts/license
+
+.PHONY: docs
+docs:
+ find src include -iname '*.[ch]' -not -path */gen/* |\
+ xargs ./scripts/warn-undocumented
+ doxygen docs/doxygen.conf
+
+RM = rm
+
+.PHONY: clean
+clean:
+ $(RM) -rf $(CLEANUP)
+
+.PHONY: clean_docs
+clean_docs:
+ $(RM) -rf docs/output
+
+.PHONY: clean_all
+clean_all: clean clean_docs
diff --git a/deps/lightening b/deps/lightening
new file mode 160000
+Subproject 4f1aa83d2cb0cc882790ea578828efe72df5ff1
diff --git a/examples/main.c b/examples/main.c
new file mode 100644
index 0000000..d33ed10
--- /dev/null
+++ b/examples/main.c
@@ -0,0 +1,32 @@
+#include <stdio.h>
+#include "../include/ejit/ejit.h"
+
+int main()
+{
+ struct ejit_func *f = ejit_create_func(EJIT_INT64, 0, NULL);
+
+ ejit_movi(f, EJIT_GPR(3), 1); // location 3 just has a constant 1 for
+ // increment
+ ejit_movi(f, EJIT_GPR(2), 0); // location 2 is i = 0
+ ejit_movi(f, EJIT_GPR(1), 1000000000); // location 1 is limit = one billion
+ ejit_movi(f, EJIT_GPR(0), 0); // location 0 is total = 0
+
+ struct ejit_label l = ejit_label(f); // top
+ ejit_addr(f, EJIT_GPR(0), EJIT_GPR(0), EJIT_GPR(2)); // add i to total
+ ejit_addr(f, EJIT_GPR(2), EJIT_GPR(2), EJIT_GPR(3)); // add 1 to i
+ struct ejit_reloc r = ejit_bltr(f, EJIT_GPR(2), EJIT_GPR(1)); // if i is less than limit
+
+ ejit_ret(f, EJIT_GPR(0)); // return total
+
+ ejit_patch(f, r, l);
+
+ /* the highest location we used was 3, so we need to request 3 location
+ * for general purpose registers in total. No floating point registers,
+ * so 0. */
+ ejit_compile_func(f, 4, 0);
+ long result = ejit_run_func(f, 0, NULL); // no args so this is fine
+ printf("%ld\n", result);
+
+ ejit_destroy_func(f);
+ return 0;
+}
diff --git a/include/ejit/ejit.h b/include/ejit/ejit.h
new file mode 100644
index 0000000..56582be
--- /dev/null
+++ b/include/ejit/ejit.h
@@ -0,0 +1,256 @@
+#ifndef EJIT_H
+#define EJIT_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <stdbool.h>
+
+enum ejit_type
+{
+ /* special case, only to be used to indicate return value of function */
+ EJIT_VOID,
+ /* free to use wherever */
+ EJIT_UINT8,
+ EJIT_INT8,
+ EJIT_UINT16,
+ EJIT_INT16,
+ EJIT_UINT32,
+ EJIT_INT32,
+ EJIT_UINT64,
+ EJIT_INT64,
+ EJIT_POINTER,
+ EJIT_FLOAT,
+ EJIT_DOUBLE,
+};
+
+struct ejit_arg {
+ union {
+ int8_t c;
+ uint8_t uc;
+ int16_t s;
+ uint16_t us;
+ int32_t i;
+ uint32_t ui;
+ int64_t l;
+ uint64_t ul;
+ float f;
+ double d;
+ void *p;
+ };
+ enum ejit_type type;
+};
+
+static inline bool ejit_int_type(enum ejit_type t)
+{
+ switch (t) {
+ case EJIT_INT8:
+ case EJIT_INT16:
+ case EJIT_INT32:
+ case EJIT_INT64:
+ case EJIT_UINT8:
+ case EJIT_UINT16:
+ case EJIT_UINT32:
+ case EJIT_UINT64:
+ case EJIT_POINTER:
+ return true;
+ default:
+ }
+
+ return false;
+}
+
+static inline bool ejit_float_type(enum ejit_type t)
+{
+ switch (t) {
+ case EJIT_FLOAT:
+ case EJIT_DOUBLE:
+ return true;
+ default:
+ }
+
+ return false;
+}
+
+static inline struct ejit_arg ejit_build_arg(enum ejit_type type, long x)
+{
+ assert(ejit_int_type(type));
+
+ struct ejit_arg a;
+ a.type = type;
+
+ switch (type) {
+ case EJIT_INT8: a.c = x; break;
+ case EJIT_INT16: a.s = x; break;
+ case EJIT_INT32: a.i = x; break;
+ case EJIT_INT64: a.l = x; break;
+ case EJIT_UINT8: a.uc = x; break;
+ case EJIT_UINT16: a.us = x; break;
+ case EJIT_UINT32: a.ui = x; break;
+ case EJIT_UINT64: a.ul = x; break;
+ case EJIT_POINTER: a.p = (void *)x; break;
+ default: abort();
+ }
+
+ return a;
+}
+
+static inline struct ejit_arg ejit_build_arg_f(enum ejit_type type, double x)
+{
+ assert(ejit_float_type(type));
+
+ struct ejit_arg a;
+ a.type = type;
+
+ switch (type) {
+ case EJIT_FLOAT: a.f = x; break;
+ case EJIT_DOUBLE: a.d = x; break;
+ default: abort();
+ }
+
+ return a;
+}
+
+#define EJIT_C(x) ((struct ejit_arg){.c = (int8_t)(x), .type = EJIT_INT8})
+
+/* register allocator could be just pushing everything above V0 or whatever onto
+ * the stack, heh */
+struct ejit_gpr {
+ size_t r;
+};
+
+struct ejit_fpr {
+ size_t f;
+};
+
+struct ejit_reloc {
+ size_t insn;
+};
+
+struct ejit_label {
+ size_t addr;
+};
+
+struct ejit_operand {
+ enum {
+ EJIT_OPERAND_GPR,
+ EJIT_OPERAND_FPR,
+ EJIT_OPERAND_IMM,
+ EJIT_OPERAND_FLT,
+ } kind;
+
+ enum ejit_type type;
+
+ union {
+ long r;
+ double d;
+ };
+};
+
+struct ejit_func;
+
+struct ejit_func *ejit_create_func(enum ejit_type rtype, size_t argc, const struct ejit_operand args[argc]);
+void ejit_compile_func(struct ejit_func *f, size_t gpr, size_t fpr);
+long ejit_run_func(struct ejit_func *f, size_t argc, struct ejit_arg args[argc]);
+double ejit_run_func_f(struct ejit_func *f, size_t argc, struct ejit_arg args[argc]);
+
+void ejit_destroy_func(struct ejit_func *s);
+
+#define EJIT_GPR(x) ((struct ejit_gpr){.r = (x)})
+#define EJIT_FPR(x) ((struct ejit_fpr){.f = (x)})
+
+#define EJIT_OPERAND_GPR(x, type) (struct ejit_operand { .kind = EJIT_OPERAND_GPR, .r = (long)(x), .type = (type)})
+#define EJIT_OPERAND_FPR(x, type) (struct ejit_operand { .kind = EJIT_OPERAND_FPR, .r = (long)(x)})
+#define EJIT_OPERAND_IMM(x, type) (struct ejit_operand { .kind = EJIT_OPERAND_IMM, .r = (long)(x)})
+#define EJIT_OPERAND_FLT(x, type) (struct ejit_operand { .kind = EJIT_OPERAND_FLT, .r = (double)(x)})
+
+typedef long (*ejit_escape_t)(size_t argc, const struct ejit_arg args[argc]);
+typedef double (*ejit_escape_f_t)(size_t argc, const struct ejit_arg args[argc]);
+
+struct ejit_label ejit_label(struct ejit_func *s);
+
+void ejit_calli(struct ejit_func *s, struct ejit_func *f, size_t argc, const struct ejit_operand args[argc]);
+
+void ejit_calli_f(struct ejit_func *s, struct ejit_func *f, size_t argc, const struct ejit_operand args[argc]);
+
+void ejit_escapei(struct ejit_func *s, ejit_escape_t f, size_t argc, const struct ejit_operand args[argc]);
+
+void ejit_escapei_f(struct ejit_func *s, ejit_escape_f_t f, size_t argc, const struct ejit_operand args[argc]);
+
+void ejit_ret(struct ejit_func *s, struct ejit_gpr r0);
+void ejit_ret_f(struct ejit_func *s, struct ejit_fpr r0);
+void ejit_ret_i(struct ejit_func *s, long i);
+void ejit_ret_fi(struct ejit_func *s, double f);
+
+/* move from r1 to r0 */
+void ejit_movi(struct ejit_func *s, struct ejit_gpr r0, long i);
+void ejit_movr(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1);
+void ejit_movr_d(struct ejit_func *s, struct ejit_fpr r0, struct ejit_fpr r1);
+
+void ejit_ldi_8(struct ejit_func *s, struct ejit_gpr r0, void *p);
+void ejit_ldi_16(struct ejit_func *s, struct ejit_gpr r0, void *p);
+void ejit_ldi_32(struct ejit_func *s, struct ejit_gpr r0, void *p);
+void ejit_ldi_64(struct ejit_func *s, struct ejit_gpr r0, void *p);
+
+void ejit_ldi_u8(struct ejit_func *s, struct ejit_gpr r0, void *p);
+void ejit_ldi_u16(struct ejit_func *s, struct ejit_gpr r0, void *p);
+void ejit_ldi_u32(struct ejit_func *s, struct ejit_gpr r0, void *p);
+void ejit_ldi_u64(struct ejit_func *s, struct ejit_gpr r0, void *p);
+
+void ejit_ldi_f(struct ejit_func *s, struct ejit_fpr r0, void *p);
+void ejit_ldi_d(struct ejit_func *s, struct ejit_fpr r0, void *p);
+
+/* from r1 + o to r0 */
+void ejit_ldxi_8(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+void ejit_ldxi_16(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+void ejit_ldxi_32(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+void ejit_ldxi_64(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+
+void ejit_ldxi_uc(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+void ejit_ldxi_us(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+void ejit_ldxi_ui(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+void ejit_ldxi_ul(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+
+void ejit_ldxi_f(struct ejit_func *s, struct ejit_fpr r0, struct ejit_gpr r1, long o);
+void ejit_ldxi_d(struct ejit_func *s, struct ejit_fpr r0, struct ejit_gpr r1, long o);
+
+void ejit_sti_8(struct ejit_func *s, struct ejit_gpr r0, void *p);
+void ejit_sti_16(struct ejit_func *s, struct ejit_gpr r0, void *p);
+void ejit_sti_32(struct ejit_func *s, struct ejit_gpr r0, void *p);
+void ejit_sti_64(struct ejit_func *s, struct ejit_gpr r0, void *p);
+
+void ejit_sti_u8(struct ejit_func *s, struct ejit_gpr r0, void *p);
+void ejit_sti_u16(struct ejit_func *s, struct ejit_gpr r0, void *p);
+void ejit_sti_u32(struct ejit_func *s, struct ejit_gpr r0, void *p);
+void ejit_sti_u64(struct ejit_func *s, struct ejit_gpr r0, void *p);
+
+void ejit_sti_f(struct ejit_func *s, struct ejit_fpr r0, void *p);
+void ejit_sti_d(struct ejit_func *s, struct ejit_fpr r0, void *p);
+
+/* from r0 to r1 + o */
+void ejit_stxi_8(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+void ejit_stxi_16(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+void ejit_stxi_32(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+void ejit_stxi_64(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+
+void ejit_stxi_u8(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+void ejit_stxi_u16(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+void ejit_stxi_u32(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+void ejit_stxi_u64(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, long o);
+
+void ejit_stxi_f(struct ejit_func *s, struct ejit_fpr r0, struct ejit_gpr r1, long o);
+void ejit_stxi_d(struct ejit_func *s, struct ejit_fpr r0, struct ejit_gpr r1, long o);
+
+void ejit_addr(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, struct ejit_gpr r2);
+void ejit_addr_f(struct ejit_func *s, struct ejit_fpr r0, struct ejit_fpr r1, struct ejit_fpr r2);
+
+void ejit_subr(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, struct ejit_gpr r2);
+void ejit_subr_f(struct ejit_func *s, struct ejit_fpr r0, struct ejit_fpr r1, struct ejit_fpr r2);
+
+/** @todo branches, should I do the nifty label trick again? */
+struct ejit_reloc ejit_bltr(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1);
+
+void ejit_patch(struct ejit_func *s, struct ejit_reloc r, struct ejit_label l);
+
+#endif /* EJIT_H */
diff --git a/scripts/gen-deps b/scripts/gen-deps
new file mode 100755
index 0000000..f45707c
--- /dev/null
+++ b/scripts/gen-deps
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+PREFIX=
+COMPILE=COMPILE
+LINT=LINT
+BUILD=build/
+
+while getopts "p:c:b:l:" opt; do
+ case "$opt" in
+ p) PREFIX="$OPTARG"_;;
+ c) COMPILE="$OPTARG";;
+ l) LINT="$OPTARG";;
+ b) BUILD=build/"$OPTARG";;
+ *) echo "unrecognised option -$OPTARG" >&2; exit 1;;
+ esac
+done
+
+shift $((OPTIND - 1))
+
+# create all subdirectories
+mkdir -p $(echo "${@}" | tr ' ' '\n' | sed "s|[^/]*$||;s|^|${BUILD}/|" | uniq)
+
+for s in ${@}
+do
+ obj="${BUILD}/${s%.*}.o"
+ lint="${obj}.l"
+ dep="${obj}.d"
+
+ echo "${PREFIX}OBJS += ${obj}" >> deps.mk
+ echo "${PREFIX}LINTS += ${lint}" >> deps.mk
+ echo "${dep}:" >> deps.mk
+ echo "-include ${dep}" >> deps.mk
+ echo "${obj}: ${s}" >> deps.mk
+ echo " \$(${COMPILE}) -c ${s} -o ${obj}" >> deps.mk
+ echo "${lint}: ${s}" >> deps.mk
+ echo " \$(${LINT}) -c ${s} -o /dev/null" >> deps.mk
+done
diff --git a/scripts/license b/scripts/license
new file mode 100755
index 0000000..53bd5da
--- /dev/null
+++ b/scripts/license
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+SPDX="/* SPDX-License-Identifier: copyleft-next-0.3.1 */"
+
+for f in "$@"
+do
+ if [ "$(head -1 "$f")" != "${SPDX}" ]
+ then
+ sed -i "1i${SPDX}\n" "$f"
+ fi
+
+ if ! grep 'Copyright' "$f" > /dev/null
+ then
+ echo "Missing copyright info in $f"
+ fi
+done
diff --git a/scripts/makefile b/scripts/makefile
new file mode 100644
index 0000000..484d04f
--- /dev/null
+++ b/scripts/makefile
@@ -0,0 +1,71 @@
+# this could be done better
+RELEASE ?= 0
+OPTFLAGS != [ "$(RELEASE)" != "0" ] \
+ && echo "-O2" \
+ || echo "-O0"
+
+DEBUG ?= 1
+DEBUGFLAGS != [ "$(DEBUG)" != "0" ] \
+ && echo "-DDEBUG=1" \
+ || echo "-DNDEBUG=1"
+
+ASSERT ?= 1
+ASSERTFLAGS != [ "$(ASSERT)" != "0" ] \
+ && echo "-DASSERT=1" \
+ || echo
+
+DEPFLAGS = -MT $@ -MMD -MP -MF $@.d
+LINTFLAGS := -fsyntax-only
+PREPROCESS := -E
+
+LLVM ?= 0
+BUILD := build
+
+all: ejit.o
+
+# default values, overwrite if/when needed
+CROSS_COMPILE :=
+
+OBJCOPY != [ "$(LLVM)" != "0" ] \
+ && echo llvm-objcopy \
+ || echo $(CROSS_COMPILE)objcopy
+
+COMPILER != [ "$(LLVM)" != "0" ] \
+ && echo clang --target="$(CROSS_COMPILE)" \
+ || echo $(CROSS_COMPILE)gcc
+
+
+OBFLAGS := -g
+# interpreter is allowed to have uninitialized values
+WARNFLAGS := -Wall -Wextra -Wno-maybe-uninitialized
+
+COMPILE_FLAGS := $(CFLAGS) $(WARNFLAGS) $(OPTFLAGS) $(OBFLAGS) $(ASSERTFLAGS) \
+ $(DEBUGFLAGS)
+
+INCLUDE_FLAGS := -I include
+
+COMPILE = $(COMPILER) \
+ $(COMPILE_FLAGS) $(DEPFLAGS) $(INCLUDE_FLAGS)
+
+LINT = $(COMPILER) \
+ $(COMPILE_FLAGS) $(LINTFLAGS) $(INCLUDE_FLAGS)
+
+UBSAN ?= 0
+UBSAN_FLAGS != [ "$(UBSAN)" != "0" ] \
+ && echo -fsanitize=undefined \
+ || echo
+
+COMPILE_EJIT = $(COMPILE) $(EJIT_FLAGS)
+
+-include deps.mk
+
+ejit.o: $(EJIT_OBJS)
+ ld -relocatable $(EJIT_OBJS) -o $@
+
+examples: examples/exec
+examples/exec: examples/main.c ejit.o
+ $(COMPILE_EJIT) examples/main.c ejit.o -o $@
+
+# might lint some common things twice
+.PHONY:
+lint: $(TRISCV_LINTS)
diff --git a/scripts/warn-undocumented b/scripts/warn-undocumented
new file mode 100755
index 0000000..db22249
--- /dev/null
+++ b/scripts/warn-undocumented
@@ -0,0 +1,7 @@
+#!/bin/sh
+# look through all files for either @file or \file
+for file in $@
+do
+ grep -c '[@\]file' "$file" |\
+ awk -F':' "\$1 == 0 {print \"Undocumented file: $file\"}"
+done
diff --git a/src/common.h b/src/common.h
new file mode 100644
index 0000000..21e881b
--- /dev/null
+++ b/src/common.h
@@ -0,0 +1,126 @@
+#ifndef EJIT_COMMON_H
+#define EJIT_COMMON_H
+
+#include <stdbool.h>
+#include "vec.h"
+
+enum ejit_opcode {
+ MOVI,
+ MOVR,
+ MOVR_F,
+
+ LDI8,
+ LDI16,
+ LDI32,
+ LDI64,
+ LDIU8,
+ LDIU16,
+ LDIU32,
+ LDIU64,
+ LDIF,
+ LDID,
+
+ LDXI8,
+ LDXI16,
+ LDXI32,
+ LDXI64,
+ LDXIU8,
+ LDXIU16,
+ LDXIU32,
+ LDXIU64,
+ LDXIF,
+ LDXID,
+
+ STI8,
+ STI16,
+ STI32,
+ STI64,
+ STIU8,
+ STIU16,
+ STIU32,
+ STIU64,
+ STIF,
+ STID,
+
+ STXI8,
+ STXI16,
+ STXI32,
+ STXI64,
+ STXIU8,
+ STXIU16,
+ STXIU32,
+ STXIU64,
+ STXIF,
+ STXID,
+
+ ADDR,
+ ADDR_F,
+ SUBR,
+ SUBR_F,
+
+ BLTR,
+
+ PARAM,
+ PARAM_F,
+
+ ARG,
+ ARG_I,
+ ARG_F,
+ ARG_FI,
+
+ ESCAPEI,
+ ESCAPEI_F,
+
+ CALLI,
+ CALLI_F,
+
+ RET,
+ RET_I,
+ RET_F,
+ RET_FI,
+
+ RETVAL,
+ RETVAL_F,
+
+ START,
+ END,
+
+ OPCODE_COUNT,
+};
+
+struct ejit_insn {
+ union {
+ enum ejit_opcode op;
+ void *addr;
+ };
+
+ size_t r0;
+ size_t r1;
+ union {
+ size_t r2;
+ void *p;
+ long o;
+ double d;
+ };
+};
+
+struct ejit_func {
+ struct vec insns;
+ enum ejit_type rtype;
+
+ size_t gpr;
+ size_t fpr;
+
+ void *arena;
+ size_t size;
+};
+
+
+union interp_ret {
+ long r;
+ double d;
+};
+
+union interp_ret ejit_interp(struct ejit_func *f, size_t argc, struct ejit_arg args[argc], bool run, void ***labels_wb);
+
+#endif /* EJIT_COMMON_H */
diff --git a/src/ejit.c b/src/ejit.c
new file mode 100644
index 0000000..bd5a652
--- /dev/null
+++ b/src/ejit.c
@@ -0,0 +1,190 @@
+#include <assert.h>
+#include <sys/mman.h>
+
+#include <ejit/ejit.h>
+
+#include "common.h"
+
+static void emit_insn_i(struct ejit_func *f, enum ejit_opcode op, size_t r0, size_t r1, long o)
+{
+ struct ejit_insn i = {.op = op, .r0 = r0, .r1 = r1, .o = o};
+ vec_append(&f->insns, &i);
+}
+
+static void emit_insn_r(struct ejit_func *f, enum ejit_opcode op, size_t r0, size_t r1, size_t r2)
+{
+ struct ejit_insn i = {.op = op, .r0 = r0, .r1 = r1, .r2 = r2};
+ vec_append(&f->insns, &i);
+}
+
+static void emit_insn_p(struct ejit_func *f, enum ejit_opcode op, size_t r0, size_t r1, void *p)
+{
+ struct ejit_insn i = {.op = op, .r0 = r0, .r1 = r1, .p = p};
+ vec_append(&f->insns, &i);
+}
+
+static void emit_insn_f(struct ejit_func *f, enum ejit_opcode op, size_t r0, size_t r1, double d)
+{
+ struct ejit_insn i = {.op = op, .r0 = r0, .r1 = r1, .d = d};
+ vec_append(&f->insns, &i);
+}
+
+struct ejit_func *ejit_create_func(enum ejit_type rtype, size_t argc, const struct ejit_operand args[argc])
+{
+ struct ejit_func *f = malloc(sizeof(struct ejit_func));
+ assert(f);
+
+ f->rtype = rtype;
+
+ f->insns = vec_create(sizeof(struct ejit_insn));
+ f->arena = NULL;
+ f->size = 0;
+
+ emit_insn_i(f, START, 0, 0, 0);
+
+ for (size_t i = 0; i < argc; ++i) {
+ switch (args[i].kind) {
+ case EJIT_OPERAND_GPR: emit_insn_i(f, PARAM, args[i].r, 0, i); break;
+ case EJIT_OPERAND_FPR: emit_insn_i(f, PARAM_F, args[i].r, 0, i); break;
+ default: abort();
+ }
+ }
+
+ return f;
+}
+
+void ejit_compile_func(struct ejit_func *f, size_t gpr, size_t fpr)
+{
+ f->gpr = gpr;
+ f->fpr = fpr;
+ /** @todo try to implement JIT */
+ /* otherwise, convert opcodes to address labels */
+
+ void **labels;
+ /* just get labels, don't actually run anything yet */
+ ejit_interp(f, 0, NULL, false, &labels);
+ foreach_vec(ii, f->insns) {
+ struct ejit_insn i = vect_at(struct ejit_insn, f->insns, ii);
+ i.addr = labels[i.op];
+ vect_at(struct ejit_insn, f->insns, ii) = i;
+ }
+}
+
+void ejit_destroy_func(struct ejit_func *f)
+{
+ if (f->arena)
+ munmap(f->arena, f->size);
+
+ vec_destroy(&f->insns);
+ free(f);
+}
+
+struct ejit_label ejit_label(struct ejit_func *f)
+{
+ return (struct ejit_label){.addr = vec_len(&f->insns)};
+}
+
+void ejit_patch(struct ejit_func *f, struct ejit_reloc r, struct ejit_label l)
+{
+ struct ejit_insn i = vect_at(struct ejit_insn, f->insns, r.insn);
+ /** @todo some assert that checks the opcode? */
+ i.o = l.addr;
+ vect_at(struct ejit_insn, f->insns, r.insn) = i;
+}
+
+void ejit_calli(struct ejit_func *s, struct ejit_func *f, size_t argc, const struct ejit_operand args[argc])
+{
+ for (size_t i = 0; i < argc; ++i) {
+ switch (args[i].kind) {
+ case EJIT_OPERAND_GPR: emit_insn_i(s, ARG, args[i].r, args[i].type, 0); break;
+ case EJIT_OPERAND_FPR: emit_insn_i(s, ARG_F, args[i].r, args[i].type, 0); break;
+ case EJIT_OPERAND_IMM: emit_insn_i(s, ARG_I, 0, args[i].type, args[i].r); break;
+ case EJIT_OPERAND_FLT: emit_insn_f(s, ARG_FI, 0, args[i].type, args[i].d); break;
+ default: abort();
+ }
+ }
+
+ emit_insn_p(s, CALLI, 0, 0, f);
+}
+
+void ejit_escapei(struct ejit_func *s, ejit_escape_t f, size_t argc, const struct ejit_operand args[argc])
+{
+ for (size_t i = 0; i < argc; ++i) {
+ switch (args[i].kind) {
+ case EJIT_OPERAND_GPR: emit_insn_i(s, ARG, args[i].r, 0, 0); break;
+ case EJIT_OPERAND_FPR: emit_insn_i(s, ARG_F, args[i].r, 0, 0); break;
+ case EJIT_OPERAND_IMM: emit_insn_i(s, ARG_I, 0, 0, args[i].r); break;
+ case EJIT_OPERAND_FLT: emit_insn_f(s, ARG_FI, 0, 0, args[i].d); break;
+ default: abort();
+ }
+ }
+
+ emit_insn_p(s, ESCAPEI, 0, 0, f);
+}
+
+void ejit_escapei_f(struct ejit_func *s, ejit_escape_f_t f, size_t argc, const struct ejit_operand args[argc])
+{
+ for (size_t i = 0; i < argc; ++i) {
+ switch (args[i].kind) {
+ case EJIT_OPERAND_GPR: emit_insn_i(s, ARG, args[i].r, 0, 0); break;
+ case EJIT_OPERAND_FPR: emit_insn_i(s, ARG_F, args[i].r, 0, 0); break;
+ case EJIT_OPERAND_IMM: emit_insn_i(s, ARG_I, 0, 0, args[i].r); break;
+ case EJIT_OPERAND_FLT: emit_insn_f(s, ARG_FI, 0, 0, args[i].d); break;
+ default: abort();
+ }
+ }
+
+ emit_insn_p(s, ESCAPEI_F, 0, 0, f);
+}
+
+void ejit_ret(struct ejit_func *s, struct ejit_gpr r0)
+{
+ emit_insn_r(s, RET, r0.r, 0, 0);
+}
+
+void ejit_ret_f(struct ejit_func *s, struct ejit_fpr r0)
+{
+ emit_insn_r(s, RET_F, r0.f, 0, 0);
+}
+
+void ejit_ret_i(struct ejit_func *s, long i)
+{
+ emit_insn_i(s, RET_I, 0, 0, i);
+}
+
+void ejit_ret_fi(struct ejit_func *s, double f)
+{
+ emit_insn_f(s, RET_FI, 0, 0, f);
+}
+
+void ejit_addr(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, struct ejit_gpr r2)
+{
+ emit_insn_r(s, ADDR, r0.r, r1.r, r2.r);
+}
+
+void ejit_addr_f(struct ejit_func *s, struct ejit_fpr r0, struct ejit_fpr r1, struct ejit_fpr r2)
+{
+ emit_insn_r(s, ADDR_F, r0.f, r1.f, r2.f);
+}
+
+void ejit_subr(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1, struct ejit_gpr r2)
+{
+ emit_insn_r(s, SUBR, r0.r, r1.r, r2.r);
+}
+
+void ejit_subr_f(struct ejit_func *s, struct ejit_fpr r0, struct ejit_fpr r1, struct ejit_fpr r2)
+{
+ emit_insn_r(s, SUBR_F, r0.f, r1.f, r2.f);
+}
+
+void ejit_movi(struct ejit_func *s, struct ejit_gpr r0, long o)
+{
+ emit_insn_i(s, MOVI, r0.r, 0, o);
+}
+
+struct ejit_reloc ejit_bltr(struct ejit_func *s, struct ejit_gpr r0, struct ejit_gpr r1)
+{
+ struct ejit_label label = ejit_label(s);
+ emit_insn_i(s, BLTR, r0.r, r1.r, 0);
+ return (struct ejit_reloc){.insn = label.addr};
+}
diff --git a/src/interp.c b/src/interp.c
new file mode 100644
index 0000000..4678aa1
--- /dev/null
+++ b/src/interp.c
@@ -0,0 +1,190 @@
+#include <ejit/ejit.h>
+
+#include "common.h"
+
+
+union interp_ret ejit_interp(struct ejit_func *f, size_t argc, struct ejit_arg args[argc], bool run, void ***labels_wb)
+{
+ static void *labels[OPCODE_COUNT] = {
+ [MOVI] = &&movi,
+ [MOVR] = &&movr,
+ [MOVR_F] = &&movr_f,
+
+ [ADDR] = &&addr,
+ [ADDR_F] = &&addr_f,
+ [SUBR] = &&subr,
+ [SUBR_F] = &&subr_f,
+
+ [BLTR] = &&bltr,
+
+ [RET] = &&ret,
+ [RET_I] = &&ret_i,
+ [RET_F] = &&ret_f,
+ [RET_FI] = &&ret_fi,
+
+ [ARG] = &&arg,
+ [ARG_I] = &&arg_i,
+ [ARG_F] = &&arg_f,
+ [ARG_FI] = &&arg_fi,
+
+ [PARAM] = &&param,
+ [PARAM_F] = &&param_f,
+
+ [CALLI] = &&calli,
+ [CALLI_F] = &&calli_f,
+
+ [START] = &&start,
+ [END] = &&end,
+ };
+
+ if (!run) {
+ *labels_wb = labels;
+ return (union interp_ret){.r = 0};
+ }
+
+ /** @todo this can be optimized by for example using a common buffer
+ * 'stack' */
+ long *gpr = malloc(f->gpr * sizeof(long));
+ long *fpr = malloc(f->fpr * sizeof(long));
+ struct vec call_args = vec_create(sizeof(struct ejit_arg));
+ struct ejit_insn *insns = f->insns.buf;
+
+ struct ejit_insn i;
+ long retval = 0; double retval_f = 0.;
+ size_t pc = 0;
+
+#define DO(x) x: { i = insns[pc];
+#define JUMP(a) goto *insns[pc = a].addr;
+#define DISPATCH() } goto *insns[++pc].addr;
+
+ DO(start);
+ DISPATCH();
+
+ DO(end);
+ goto out_int;
+ DISPATCH();
+
+ DO(movi);
+ gpr[i.r0] = i.o;
+ DISPATCH();
+
+ DO(movr);
+ gpr[i.r0] = gpr[i.r1];
+ DISPATCH();
+
+ DO(movr_f);
+ fpr[i.r0] = fpr[i.r1];
+ DISPATCH();
+
+ DO(addr);
+ gpr[i.r0] = gpr[i.r1] + gpr[i.r2];
+ DISPATCH();
+
+ DO(addr_f);
+ fpr[i.r0] = fpr[i.r1] + fpr[i.r2];
+ DISPATCH();
+
+ DO(subr);
+ gpr[i.r0] = gpr[i.r1] - gpr[i.r2];
+ DISPATCH();
+
+ DO(subr_f);
+ fpr[i.r0] = fpr[i.r1] - fpr[i.r2];
+ DISPATCH();
+
+ DO(bltr);
+ if (gpr[i.r0] < gpr[i.r1])
+ JUMP(i.o);
+
+ DISPATCH();
+
+ DO(param);
+ gpr[i.r0] = args[i.o].l;
+ DISPATCH();
+
+ DO(param_f);
+ fpr[i.r0] = args[i.o].d;
+ DISPATCH();
+
+ DO(arg);
+ struct ejit_arg a = ejit_build_arg(i.r1, gpr[i.r0]);
+ vec_append(&call_args, &a);
+ DISPATCH();
+
+ DO(arg_i);
+ struct ejit_arg a = ejit_build_arg(i.r1, i.o);
+ vec_append(&call_args, &a);
+ DISPATCH();
+
+ DO(arg_f);
+ struct ejit_arg a = ejit_build_arg_f(i.r1, fpr[i.r0]);
+ vec_append(&call_args, &a);
+ DISPATCH();
+
+ DO(arg_fi);
+ struct ejit_arg a = ejit_build_arg_f(i.r1, i.d);
+ vec_append(&call_args, &a);
+ DISPATCH();
+
+ DO(calli);
+ struct ejit_func *f = i.p;
+ retval = ejit_run_func(f, vec_len(&call_args), call_args.buf);
+ vec_reset(&call_args);
+ DISPATCH();
+
+ DO(calli_f);
+ struct ejit_func *f = i.p;
+ retval_f = ejit_run_func_f(f, vec_len(&call_args), call_args.buf);
+ vec_reset(&call_args);
+ DISPATCH();
+
+ /* dispatch is technically unnecessary for returns, but keep it for
+ * symmetry */
+ DO(ret);
+ retval = gpr[i.r0];
+ goto out_int;
+ DISPATCH();
+
+ DO(ret_i);
+ retval = i.o;
+ goto out_int;
+ DISPATCH();
+
+ DO(ret_f);
+ retval_f = fpr[i.r0];
+ goto out_float;
+ DISPATCH();
+
+ DO(ret_fi);
+ retval_f = i.d;
+ goto out_float;
+ DISPATCH();
+
+#undef DISPATCH
+#undef JUMP
+#undef DO
+
+out_int:
+ vec_destroy(&call_args);
+ free(gpr);
+ free(fpr);
+ return (union interp_ret){.r = retval};
+
+out_float:
+ vec_destroy(&call_args);
+ free(gpr);
+ free(fpr);
+ return (union interp_ret){.d = retval_f};
+}
+
+long ejit_run_func(struct ejit_func *f, size_t argc, struct ejit_arg args[argc])
+{
+ assert(f->rtype == EJIT_VOID || ejit_int_type(f->rtype));
+ return ejit_interp(f, argc, args, true, NULL).r;
+}
+
+double ejit_run_func_f(struct ejit_func *f, size_t argc, struct ejit_arg args[argc])
+{
+ assert(ejit_float_type(f->rtype));
+ return ejit_interp(f, argc, args, true, NULL).d;
+}
diff --git a/src/source.mk b/src/source.mk
new file mode 100644
index 0000000..907befa
--- /dev/null
+++ b/src/source.mk
@@ -0,0 +1,2 @@
+LOCAL_SOURCES != echo src/*.c
+EJIT_SOURCES := $(EJIT_SOURCES) $(LOCAL_SOURCES)
diff --git a/src/vec.h b/src/vec.h
new file mode 100644
index 0000000..7a1d5cb
--- /dev/null
+++ b/src/vec.h
@@ -0,0 +1,95 @@
+#ifndef VEC_H
+#define VEC_H
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+struct vec {
+ size_t n;
+ size_t s;
+ size_t ns;
+ void *buf;
+};
+
+#define foreach_vec(iter, v) \
+ for (size_t iter = 0; iter < vec_len(&v); ++iter)
+
+#define vect_at(type, v, i) \
+ *(type *)vec_at(&v, i)
+
+#define vect_append(type, v, e) \
+ vec_append(&v, (type *)(e))
+
+#define vect_back(type, v) \
+ *(type *)vec_back(&v)
+
+#define vect_pop(type, v) \
+ *(type *)vec_pop(&v)
+
+#define vec_uninit(v) \
+ (v.buf == NULL)
+
+static inline struct vec vec_create(size_t ns)
+{
+ return (struct vec) {
+ .n = 0,
+ .s = 1,
+ .ns = ns,
+ .buf = malloc(ns),
+ };
+}
+
+static inline size_t vec_len(struct vec *v)
+{
+ return v->n;
+}
+
+static inline void *vec_at(struct vec *v, size_t i)
+{
+ assert(i < v->n && "out of vector bounds");
+ return v->buf + i * v->ns;
+}
+
+static inline void *vec_back(struct vec *v)
+{
+ assert(v->n);
+ return v->buf + (v->n - 1) * v->ns;
+}
+
+static inline void *vec_pop(struct vec *v)
+{
+ assert(v->n && "attempting to pop empty vector");
+ v->n--;
+ return v->buf + v->n * v->ns;
+}
+
+static inline void vec_append(struct vec *v, void *n)
+{
+ v->n++;
+ if (v->n >= v->s) {
+ v->s *= 2;
+ v->buf = realloc(v->buf, v->s * v->ns);
+ }
+
+ void *p = vec_at(v, v->n - 1);
+ memcpy(p, n, v->ns);
+}
+
+static inline void vec_reset(struct vec *v)
+{
+ v->n = 0;
+}
+
+static inline void vec_destroy(struct vec *v) {
+ free(v->buf);
+}
+
+typedef int (*vec_comp_t)(void *a, void *b);
+static inline void vec_sort(struct vec *v, vec_comp_t comp)
+{
+ qsort(v->buf, v->n, v->ns, (__compar_fn_t)comp);
+}
+
+#endif /* VEC_H */