summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKimplul <kimi.h.kuparinen@gmail.com>2025-05-18 17:15:57 +0300
committerKimplul <kimi.h.kuparinen@gmail.com>2025-05-18 17:15:57 +0300
commit0e6bc60ceb3d2676fae166308668680a2f675d17 (patch)
tree0f7ebe21dafb8f7546e36b91645e4e5281fff856
downloadberg-0e6bc60ceb3d2676fae166308668680a2f675d17.tar.gz
berg-0e6bc60ceb3d2676fae166308668680a2f675d17.zip
initial rough skeleton
+ Naive/fast allocation hashmap + Stores/loads are currently all checked, the idea about static analysis to skip checks is still TODO
-rw-r--r--.gitignore5
-rw-r--r--.gitmodules6
-rw-r--r--Makefile65
m---------deps/conts0
m---------deps/ejit0
-rw-r--r--include/berg/vm.h104
-rwxr-xr-xscripts/gen-deps37
-rwxr-xr-xscripts/license16
-rw-r--r--scripts/makefile69
-rwxr-xr-xscripts/warn-undocumented7
-rw-r--r--src/main.c118
-rw-r--r--src/source.mk3
-rw-r--r--src/vm.c477
13 files changed, 907 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b7bcfd3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+deps.mk
+docs/output
+build
+berg
+!include/berg
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..f3f2d67
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "deps/ejit"]
+ path = deps/ejit
+ url = https://metanimi.dy.fi/cgit/ejit
+[submodule "deps/conts"]
+ path = deps/conts
+ url = https://metanimi.dy.fi/cgit/conts
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..14e9aea
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,65 @@
+.PHONY: all
+all: setup
+ $(MAKE) -f scripts/makefile
+
+# 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: analyze
+analyze: setup
+ CC='gcc -fanalyzer' SKIP_ANALYZER='-fno-analyzer' $(MAKE) CROSS_COMPILE=
+
+.PHONY: setup
+setup: deps
+ @echo -n > deps.mk
+ @./scripts/gen-deps -p BERG -c COMPILE_BERG -b berg "$(BERG_SOURCES)"
+
+CLEANUP := build deps.mk fwd
+CLEANUP_CMD :=
+BERG_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)
+ $(MAKE) -C deps/ejit clean
+
+.PHONY: clean_docs
+clean_docs:
+ $(RM) -rf docs/output
+
+.PHONY: clean_all
+clean_all: clean clean_docs
+
+.PHONY: deps
+deps:
+ $(MAKE) -C deps/ejit
diff --git a/deps/conts b/deps/conts
new file mode 160000
+Subproject 7774ae2f8c2dca9ab2d93082856f031e78a1b5f
diff --git a/deps/ejit b/deps/ejit
new file mode 160000
+Subproject b0a6350b96f6ef3f8b2af5a385474d885a41759
diff --git a/include/berg/vm.h b/include/berg/vm.h
new file mode 100644
index 0000000..0f87452
--- /dev/null
+++ b/include/berg/vm.h
@@ -0,0 +1,104 @@
+#ifndef BERG_VM_H
+#define BERG_VM_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <assert.h>
+
+enum berg_envcall {
+ BERG_MALLOC,
+ BERG_FREE,
+ /* temporarily used for debugging, actual printf should probably be done
+ * via open/close/write/read etc */
+ BERG_PUTI32
+};
+
+enum berg_op {
+ BERG_LABEL,
+ BERG_LD32,
+ BERG_ST32,
+ BERG_CALL,
+ BERG_RET,
+ BERG_ECALL,
+ BERG_MOVI,
+ BERG_MOVR,
+};
+
+const char *berg_op_str(enum berg_op op);
+
+struct berg_insn {
+ uint16_t op;
+ uint8_t ra;
+ uint8_t rb;
+ uint8_t rc;
+ uint8_t rd;
+ union {
+ void *p;
+ int64_t imm;
+ };
+};
+
+enum berg_type {
+ BERG_VOID,
+ BERG_POINTER
+};
+
+struct berg_vm;
+struct berg_func;
+
+struct berg_gpr {
+ uint8_t r;
+};
+
+struct berg_operand {
+ enum berg_type type;
+ struct berg_gpr gpr;
+};
+
+static inline struct berg_operand berg_operand_gpr(struct berg_gpr gpr, enum berg_type type)
+{
+ return (struct berg_operand){.type = type, .gpr = gpr};
+}
+
+static inline struct berg_gpr berg_gpr(uint8_t r)
+{
+ return (struct berg_gpr){.r = r};
+}
+
+static inline struct berg_gpr berg_gpr_arg(uint8_t r)
+{
+ assert(r < 64);
+ return berg_gpr(r);
+}
+
+static inline struct berg_gpr berg_gpr_tmp(uint8_t r)
+{
+ assert(r < 128);
+ /* skip arg regs */
+ return berg_gpr(r + 64);
+}
+
+struct berg_vm *create_berg_vm();
+struct berg_func *create_berg_func(struct berg_vm *vm, enum berg_type rtype, size_t argc, const struct berg_operand args[argc]);
+long compile_berg_func(struct berg_func *f);
+long run_berg_func(struct berg_func *f);
+void destroy_berg_vm(struct berg_vm *vm);
+
+long berg_ecall(struct berg_func *f,
+ struct berg_gpr ra,
+ int64_t imm);
+
+long berg_call(struct berg_func *f,
+ struct berg_func *c);
+
+long berg_movi(struct berg_func *f,
+ struct berg_gpr ra,
+ int64_t imm);
+
+long berg_movr(struct berg_func *f,
+ struct berg_gpr ra,
+ struct berg_gpr rb);
+
+long berg_ret(struct berg_func *f);
+
+#endif /* BERG_VM_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..4543fcf
--- /dev/null
+++ b/scripts/makefile
@@ -0,0 +1,69 @@
+# this could be done better
+RELEASE ?= 0
+OPTFLAGS != [ "$(RELEASE)" != "0" ] \
+ && echo "-O2 -flto=auto" \
+ || 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: berg
+
+# default values, overwrite if/when needed
+CROSS_COMPILE :=
+
+OBJCOPY != [ "$(LLVM)" != "0" ] \
+ && echo llvm-objcopy \
+ || echo $(CROSS_COMPILE)objcopy
+
+COMPILER != [ -n "$(CROSS_COMPILE)" ] \
+ && { \
+ [ "$(LLVM)" != "0" ] \
+ && echo clang --target="$(CROSS_COMPILE)" \
+ || echo $(CROSS_COMPILE)gcc \
+ ; \
+ } \
+ || echo $(CC)
+
+
+# -rdynamic is apparently platform-dependent, not sure if I want to rely on it
+# but good enough for now
+OBFLAGS := -g -rdynamic
+WARNFLAGS := -Wall -Wextra
+
+COMPILE_FLAGS := $(CFLAGS) $(WARNFLAGS) $(OPTFLAGS) $(OBFLAGS) $(ASSERTFLAGS) \
+ $(DEBUGFLAGS)
+
+INCLUDE_FLAGS := -I include -I deps/conts/include -I deps/ejit/include
+
+COMPILE = $(COMPILER) \
+ $(COMPILE_FLAGS) $(DEPFLAGS) $(INCLUDE_FLAGS)
+
+LINT = $(COMPILER) \
+ $(COMPILE_FLAGS) $(LINTFLAGS) $(INCLUDE_FLAGS)
+
+COMPILE_BERG = $(COMPILE) $(BERG_FLAGS)
+
+-include deps.mk
+
+berg: $(BERG_OBJS) deps/ejit/libejit.a
+ $(COMPILE_BERG) $(BERG_OBJS) deps/ejit/libejit.a -o $@ -lm
+
+
+# might lint some common things twice
+.PHONY:
+lint: $(FWD_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/main.c b/src/main.c
new file mode 100644
index 0000000..d09a87b
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,118 @@
+#include <stddef.h>
+#include <berg/vm.h>
+
+#define MATRIX_SIZE 400
+
+static struct berg_func *compile_init(struct berg_vm *vm)
+{
+ struct berg_gpr A = berg_gpr(0);
+ struct berg_gpr B = berg_gpr(1);
+ struct berg_operand operands[] = {
+ /** @todo pointer bounds in type signature? */
+ berg_operand_gpr(A, BERG_POINTER),
+ berg_operand_gpr(B, BERG_POINTER),
+ };
+ struct berg_func *f = create_berg_func(vm, BERG_VOID, 2, operands);
+
+ berg_ret(f);
+ compile_berg_func(f);
+ return f;
+}
+
+static struct berg_func *compile_mult(struct berg_vm *vm)
+{
+ /* interesting, not using anything with a register means it gets treated
+ * as a oneshot reg, which is an issue for arguments. */
+ struct berg_gpr A = berg_gpr_tmp(0);
+ struct berg_gpr B = berg_gpr_tmp(1);
+ struct berg_gpr C = berg_gpr_tmp(2);
+ struct berg_operand operands[] = {
+ berg_operand_gpr(A, BERG_POINTER),
+ berg_operand_gpr(B, BERG_POINTER),
+ berg_operand_gpr(C, BERG_POINTER),
+ };
+ struct berg_func *f = create_berg_func(vm, BERG_VOID, 3, operands);
+
+ /* should be fixed in ejit itself but for now, just pretend we're doing
+ * something with the registers */
+ berg_movr(f, B, A);
+ berg_movr(f, C, B);
+ berg_movr(f, A, C);
+
+ berg_ret(f);
+ compile_berg_func(f);
+ return f;
+}
+
+static struct berg_func *compile_dump(struct berg_vm *vm)
+{
+ struct berg_gpr C = berg_gpr_tmp(0);
+ struct berg_operand operands[] = {
+ berg_operand_gpr(C, BERG_POINTER),
+ };
+ struct berg_func *f = create_berg_func(vm, BERG_VOID, 1, operands);
+
+ berg_ret(f);
+ compile_berg_func(f);
+ return f;
+}
+
+static struct berg_func *compile_main(struct berg_vm *vm, struct berg_func *init, struct berg_func *mult, struct berg_func *dump)
+{
+ struct berg_gpr T = berg_gpr_tmp(0);
+ struct berg_gpr A = berg_gpr_tmp(1);
+ struct berg_gpr B = berg_gpr_tmp(2);
+ struct berg_gpr C = berg_gpr_tmp(3);
+ struct berg_func *f = create_berg_func(vm, BERG_VOID, 0, NULL);
+
+ struct berg_gpr A0 = berg_gpr_arg(0);
+ struct berg_gpr A1 = berg_gpr_arg(1);
+ struct berg_gpr A2 = berg_gpr_arg(2);
+
+ /* alloc matrixes */
+ berg_movi(f, A0, MATRIX_SIZE * MATRIX_SIZE * sizeof(int));
+ /* ecall takes some fixed registers, 0-1 (at least for now, will have to
+ * think of a proper ABI at some point) */
+ berg_ecall(f, A, BERG_MALLOC);
+ berg_ecall(f, B, BERG_MALLOC);
+ berg_ecall(f, C, BERG_MALLOC);
+
+ /** @todo come up with ABI for calls */
+ berg_movr(f, A0, A);
+ berg_movr(f, A1, B);
+ berg_call(f, init);
+
+ berg_movr(f, A2, C);
+ berg_call(f, mult);
+
+ berg_movr(f, A0, C);
+ berg_call(f, dump);
+
+ /* we don't really do anything with the return value so just dump it
+ * into T (should I have some kind of X0 register like in riscv?) */
+ berg_movr(f, A0, A);
+ berg_ecall(f, T, BERG_FREE);
+
+ berg_movr(f, A0, B);
+ berg_ecall(f, T, BERG_FREE);
+
+ berg_movr(f, A0, C);
+ berg_ecall(f, T, BERG_FREE);
+
+ berg_ret(f);
+ compile_berg_func(f);
+ return f;
+}
+
+int main(int argc, char *argv[argc])
+{
+ struct berg_vm *vm = create_berg_vm();
+ struct berg_func *init = compile_init(vm);
+ struct berg_func *mult = compile_mult(vm);
+ struct berg_func *dump = compile_dump(vm);
+ struct berg_func *mn = compile_main(vm, init, mult, dump);
+
+ run_berg_func(mn);
+ destroy_berg_vm(vm);
+ return 0;
+}
diff --git a/src/source.mk b/src/source.mk
new file mode 100644
index 0000000..7a351da
--- /dev/null
+++ b/src/source.mk
@@ -0,0 +1,3 @@
+SRCS != echo src/*.c
+
+BERG_SOURCES += $(SRCS)
diff --git a/src/vm.c b/src/vm.c
new file mode 100644
index 0000000..fdcd71b
--- /dev/null
+++ b/src/vm.c
@@ -0,0 +1,477 @@
+#include <stdio.h>
+
+#include <ejit/ejit.h>
+#include <berg/vm.h>
+
+#define VEC_NAME funcs
+#define VEC_TYPE struct berg_func *
+#include <conts/vec.h>
+
+#define VEC_NAME insns
+#define VEC_TYPE struct berg_insn
+#include <conts/vec.h>
+
+#define VEC_NAME operands
+#define VEC_TYPE struct berg_operand
+#include <conts/vec.h>
+
+struct alloc {
+ void *base, *top;
+};
+
+struct allocs {
+ /* must always be pow2 */
+ size_t size;
+ struct alloc *buf;
+};
+
+struct berg_vm {
+ struct funcs funcs;
+ struct allocs allocs;
+};
+
+struct berg_func {
+ /* owning vm */
+ struct berg_vm *vm;
+
+ /* vm instructions */
+ struct insns insns;
+
+ /* arguments */
+ struct operands operands;
+
+ /* actual function */
+ struct ejit_func *func;
+};
+
+static struct allocs allocs_create(size_t pow2)
+{
+ size_t size = 1ULL << pow2;
+ struct alloc *buf = calloc(size, sizeof(struct alloc));
+ struct allocs allocs = {.size = size, buf = buf};
+ return allocs;
+}
+
+static size_t allocs_idx(void *ptr, size_t size)
+{
+ /* presumably all pointers have at least 16 bytes alignment, although
+ * this might only apply to amd64 (check max_alignment_t?) */
+ return ((uintptr_t)ptr >> 4) & (size - 1);
+}
+
+static long allocs_resize(struct allocs *allocs)
+{
+ /* very naive allocation strategy, a single collision causes a regrow */
+ size_t new_size = allocs->size * 2;
+
+top:
+ struct alloc *new_buf = calloc(new_size, sizeof(struct alloc));
+ if (!new_buf)
+ return -1;
+
+ for (size_t i = 0; i < allocs->size; ++i) {
+ struct alloc *alloc = &allocs->buf[i];
+ if (!alloc->base)
+ continue;
+
+ uintptr_t idx = allocs_idx(alloc->base, new_size);
+ struct alloc *new_alloc = &new_buf[idx];
+ if (new_alloc->base) {
+ /* try again */
+ free(new_buf);
+ new_size *= 2;
+ goto top;
+ }
+
+ *new_alloc = *alloc;
+ }
+
+ allocs->size = new_size;
+ allocs->buf = new_buf;
+ return 0;
+}
+
+static long allocs_insert(struct allocs *allocs, void *ptr, size_t size)
+{
+ void *top = ptr + size;
+
+ while (1) {
+ /* try to place allocation into map */
+ size_t idx = allocs_idx(ptr, allocs->size);
+ struct alloc *node = &allocs->buf[idx];
+
+ /* free slot */
+ if (node->base == NULL) {
+ node->base = ptr;
+ node->top = top;
+ return 0;
+ }
+
+ int ret = allocs_resize(allocs);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static long allocs_remove(struct allocs *allocs, void *ptr)
+{
+ uintptr_t idx = allocs_idx(ptr, allocs->size);
+ struct alloc *alloc = &allocs->buf[idx];
+ if (alloc->base != ptr)
+ return -1;
+
+ alloc->base = NULL;
+ alloc->top = NULL;
+ return 0;
+}
+
+struct berg_vm *create_berg_vm()
+{
+ struct berg_vm *vm = calloc(1, sizeof(struct berg_vm));
+ vm->funcs = funcs_create(0);
+
+ /* 2^4 elements to start with */
+ vm->allocs = allocs_create(4);
+ return vm;
+}
+
+static enum ejit_type ejit_type_from(enum berg_type type)
+{
+ switch (type) {
+ case BERG_POINTER: return EJIT_POINTER;
+ case BERG_VOID: return EJIT_VOID;
+ default: abort();
+ }
+}
+
+const uint8_t NOGPR = 255;
+/* two reserved registers for keeping track of allocation stuff. Should be
+ * fetched from VM to registers (or stack I guess?) whenever a call happens,
+ * since the size of the allocation hashmap might've changed while we weren't
+ * looking */
+const struct ejit_gpr ALLOC_MASK = EJIT_GPR(0);
+const struct ejit_gpr ALLOC_BUF = EJIT_GPR(1);
+const struct ejit_gpr VM_PTR = EJIT_GPR(2);
+
+static struct ejit_gpr ejit_gpr_from(uint8_t r)
+{
+ assert(r != NOGPR);
+ /* three reserved registers */
+ return EJIT_GPR(r + 3);
+}
+
+struct berg_func *create_berg_func(struct berg_vm *vm, enum berg_type rtype, size_t argc, const struct berg_operand args[argc])
+{
+ struct berg_func *f = calloc(1, sizeof(struct berg_func));
+ f->vm = vm;
+ f->insns = insns_create(0);
+ f->operands = operands_create(argc);
+
+ struct ejit_operand ejit_operands[argc];
+ for (size_t i = 0; i < argc; ++i) {
+ operands_append(&f->operands, args[i]);
+ ejit_operands[i] = EJIT_OPERAND_GPR(
+ ejit_gpr_from(args[i].gpr.r).r,
+ ejit_type_from(args[i].type)
+ );
+ }
+
+ f->func = ejit_create_func(ejit_type_from(rtype), argc, ejit_operands);
+ funcs_append(&vm->funcs, f);
+ return f;
+}
+
+
+static struct berg_insn insn_ori(enum berg_op op, struct berg_gpr ra, int64_t imm)
+{
+ return (struct berg_insn){
+ .op = op,
+ .ra = ra.r,
+ .rb = NOGPR,
+ .rc = NOGPR,
+ .rd = NOGPR,
+ .imm = imm
+ };
+}
+
+static struct berg_insn insn_orr(enum berg_op op, struct berg_gpr ra, struct berg_gpr rb)
+{
+ return (struct berg_insn){
+ .op = op,
+ .ra = ra.r,
+ .rb = rb.r,
+ .rc = NOGPR,
+ .rd = NOGPR,
+ .imm = 0
+ };
+}
+
+static struct berg_insn insn_o(enum berg_op op)
+{
+ return (struct berg_insn){
+ .op = op,
+ .ra = NOGPR,
+ .rb = NOGPR,
+ .rc = NOGPR,
+ .rd = NOGPR,
+ .imm = 0
+ };
+}
+
+static struct berg_insn insn_op(enum berg_op op, void *p)
+{
+ return (struct berg_insn){
+ .op = op,
+ .ra = NOGPR,
+ .rb = NOGPR,
+ .rc = NOGPR,
+ .rd = NOGPR,
+ .p = p
+ };
+}
+
+long berg_movi(struct berg_func *f, struct berg_gpr ra, int64_t imm)
+{
+ insns_append(&f->insns, insn_ori(BERG_MOVI, ra, imm));
+ /* for now */
+ return 0;
+}
+
+long berg_movr(struct berg_func *f, struct berg_gpr ra, struct berg_gpr rb)
+{
+ insns_append(&f->insns, insn_orr(BERG_MOVR, ra, rb));
+ return 0;
+}
+
+long berg_ret(struct berg_func *f)
+{
+ insns_append(&f->insns, insn_o(BERG_RET));
+ return 0;
+}
+
+long berg_ecall(struct berg_func *f, struct berg_gpr ra, int64_t imm)
+{
+ insns_append(&f->insns, insn_ori(BERG_ECALL, ra, imm));
+ return 0;
+}
+
+long berg_call(struct berg_func *f, struct berg_func *c)
+{
+ assert(f->vm == c->vm);
+ insns_append(&f->insns, insn_op(BERG_CALL, c));
+ return 0;
+}
+
+const char *berg_op_str(enum berg_op op)
+{
+#define CASE(x) case x: return #x;
+ switch (op) {
+ CASE(BERG_LABEL);
+ CASE(BERG_LD32);
+ CASE(BERG_ST32);
+ CASE(BERG_CALL);
+ CASE(BERG_RET);
+ CASE(BERG_ECALL);
+ CASE(BERG_MOVI);
+ CASE(BERG_MOVR);
+ }
+
+ return "???";
+#undef CASE
+}
+
+static long compile_ret(struct berg_func *f)
+{
+ ejit_ret(f->func);
+ return 0;
+}
+
+static long compile_movi(struct berg_func *f, struct berg_insn *i)
+{
+ ejit_movi(f->func, ejit_gpr_from(i->ra), i->imm);
+ return 0;
+}
+
+/* should really be uintptr_t... */
+static long escape_malloc(size_t argc, const struct ejit_arg args[argc])
+{
+ struct berg_vm *vm = EJIT_PARAM(argc, args, 0, struct berg_vm *);
+ size_t size = EJIT_PARAM(argc, args, 1, size_t);
+ void *ptr = malloc(size);
+ if (!ptr)
+ return 0;
+
+ if (allocs_insert(&vm->allocs, ptr, size)) {
+ free(ptr);
+ return 0;
+ }
+
+ return (long)ptr;
+}
+
+static long escape_free(size_t argc, const struct ejit_arg args[argc])
+{
+ struct berg_vm *vm = EJIT_PARAM(argc, args, 0, struct berg_vm *);
+ void *ptr = EJIT_PARAM(argc, args, 1, void *);
+ if (allocs_remove(&vm->allocs, ptr)) {
+ fprintf(stderr, "trying to free nonexistant pointer\n");
+ abort();
+ }
+
+ free(ptr);
+ return 0;
+}
+
+static long compile_malloc(struct berg_func *f, struct berg_insn *i)
+{
+ struct berg_gpr size = berg_gpr_arg(0);
+
+ ejit_movi(f->func, VM_PTR, (int64_t)f->vm);
+ struct ejit_operand args[] = {
+ EJIT_OPERAND_GPR(VM_PTR.r, EJIT_POINTER),
+ EJIT_OPERAND_GPR(ejit_gpr_from(size.r).r, EJIT_TYPE(size_t)),
+ };
+ ejit_escapei_l(f->func, escape_malloc, 2, args);
+ ejit_retval(f->func, ejit_gpr_from(i->ra));
+
+ /* reload allocation context if the hashmap has changed */
+ EJIT_LDI(f->func, size_t, ALLOC_MASK, &f->vm->allocs.size);
+ EJIT_LDI(f->func, void *, ALLOC_BUF, &f->vm->allocs.buf);
+ ejit_subi(f->func, ALLOC_MASK, ALLOC_MASK, 1);
+ return 0;
+}
+
+static long compile_free(struct berg_func *f, struct berg_insn *i)
+{
+ struct berg_gpr ptr = berg_gpr_arg(0);
+
+ ejit_movi(f->func, VM_PTR, (int64_t)f->vm);
+ struct ejit_operand args[] = {
+ EJIT_OPERAND_GPR(VM_PTR.r, EJIT_POINTER),
+ EJIT_OPERAND_GPR(ejit_gpr_from(ptr.r).r, EJIT_POINTER),
+ };
+ ejit_escapei_l(f->func, escape_free, 2, args);
+ ejit_retval(f->func, ejit_gpr_from(i->ra));
+
+ /* no need to reload anything since (at least currently) hashmap can't
+ * shrink */
+ return 0;
+}
+
+static long compile_ecall(struct berg_func *f, struct berg_insn *i)
+{
+ switch (i->imm) {
+ case BERG_MALLOC: return compile_malloc(f, i);
+ case BERG_FREE: return compile_free(f, i);
+ }
+
+ fprintf(stderr, "unknown envcall: %lli\n", (long long)i->imm);
+ return -1;
+}
+
+/** @todo this doesn't really work for calling functions via registers, unsure
+ * how that situation should be handled */
+static long compile_call(struct berg_func *f, struct berg_insn *i)
+{
+ struct berg_func *c = i->p;
+
+ size_t operands = operands_len(&c->operands);
+ struct ejit_operand ejit_operands[operands];
+ for (size_t i = 0; i < operands; ++i) {
+ struct berg_gpr reg = berg_gpr_arg(i);
+ struct berg_operand *operand = operands_at(&c->operands, i);
+ ejit_operands[i] = EJIT_OPERAND_GPR(
+ ejit_gpr_from(reg.r).r,
+ ejit_type_from(operand->type)
+ );
+ }
+
+ ejit_calli(f->func, c->func, operands, ejit_operands);
+
+ /* reload allocation map since we don't know if something was maybe
+ * freed */
+ EJIT_LDI(f->func, size_t, ALLOC_MASK, &f->vm->allocs.size);
+ EJIT_LDI(f->func, void *, ALLOC_BUF, &f->vm->allocs.buf);
+ ejit_subi(f->func, ALLOC_MASK, ALLOC_MASK, 1);
+ return 0;
+}
+
+static long compile_movr(struct berg_func *f, struct berg_insn *i)
+{
+ ejit_movr(f->func, ejit_gpr_from(i->ra), ejit_gpr_from(i->rb));
+ return 0;
+}
+
+long compile_berg_func(struct berg_func *f)
+{
+ /** @todo eventually should make this be block-specific */
+ long ret = 0;
+ foreach(insns, i, &f->insns) switch (i->op) {
+ case BERG_RET:
+ if ((ret = compile_ret(f)))
+ return ret;
+ break;
+
+ case BERG_MOVI:
+ if ((ret = compile_movi(f, i)))
+ return ret;
+ break;
+
+ case BERG_MOVR:
+ if ((ret = compile_movr(f, i)))
+ return ret;
+ break;
+
+ case BERG_CALL:
+ if ((ret = compile_call(f, i)))
+ return ret;
+ break;
+
+ case BERG_ECALL:
+ if ((ret = compile_ecall(f, i)))
+ return ret;
+ break;
+
+ default:
+ fprintf(stderr, "unhandled op %s\n", berg_op_str(i->op));
+ return -1;
+ }
+
+ ejit_compile_func(f->func);
+ return 0;
+}
+
+long run_berg_func(struct berg_func *f)
+{
+ struct ejit_arg arg = ejit_run_func(f->func, 0, NULL);
+ /* for now */
+ assert(arg.type == EJIT_VOID);
+ return 0;
+}
+
+static void destroy_berg_func(struct berg_func *f)
+{
+ insns_destroy(&f->insns);
+ operands_destroy(&f->operands);
+ ejit_destroy_func(f->func);
+ free(f);
+}
+
+void destroy_berg_vm(struct berg_vm *vm)
+{
+ foreach(funcs, f, &vm->funcs) {
+ destroy_berg_func(*f);
+ }
+ funcs_destroy(&vm->funcs);
+
+ for (size_t i = 0; i < vm->allocs.size; ++i) {
+ struct alloc a = vm->allocs.buf[i];
+ if (a.base)
+ free(a.base);
+ }
+
+ free(vm->allocs.buf);
+ free(vm);
+}