diff options
author | Kimplul <kimi.h.kuparinen@gmail.com> | 2025-05-18 17:15:57 +0300 |
---|---|---|
committer | Kimplul <kimi.h.kuparinen@gmail.com> | 2025-05-18 17:15:57 +0300 |
commit | 0e6bc60ceb3d2676fae166308668680a2f675d17 (patch) | |
tree | 0f7ebe21dafb8f7546e36b91645e4e5281fff856 | |
download | berg-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-- | .gitignore | 5 | ||||
-rw-r--r-- | .gitmodules | 6 | ||||
-rw-r--r-- | Makefile | 65 | ||||
m--------- | deps/conts | 0 | ||||
m--------- | deps/ejit | 0 | ||||
-rw-r--r-- | include/berg/vm.h | 104 | ||||
-rwxr-xr-x | scripts/gen-deps | 37 | ||||
-rwxr-xr-x | scripts/license | 16 | ||||
-rw-r--r-- | scripts/makefile | 69 | ||||
-rwxr-xr-x | scripts/warn-undocumented | 7 | ||||
-rw-r--r-- | src/main.c | 118 | ||||
-rw-r--r-- | src/source.mk | 3 | ||||
-rw-r--r-- | src/vm.c | 477 |
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); +} |