diff options
-rw-r--r-- | .gitignore | 6 | ||||
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | Makefile | 60 | ||||
m--------- | deps/lightening | 0 | ||||
-rw-r--r-- | examples/main.c | 32 | ||||
-rw-r--r-- | include/ejit/ejit.h | 256 | ||||
-rwxr-xr-x | scripts/gen-deps | 37 | ||||
-rwxr-xr-x | scripts/license | 16 | ||||
-rw-r--r-- | scripts/makefile | 71 | ||||
-rwxr-xr-x | scripts/warn-undocumented | 7 | ||||
-rw-r--r-- | src/common.h | 126 | ||||
-rw-r--r-- | src/ejit.c | 190 | ||||
-rw-r--r-- | src/interp.c | 190 | ||||
-rw-r--r-- | src/source.mk | 2 | ||||
-rw-r--r-- | src/vec.h | 95 |
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] = &¶m, + [PARAM_F] = &¶m_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 */ |