From 664006b1c42aae84a3c749d9b71c1047e0b8ffcf Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Thu, 2 Mar 2023 20:03:52 -0800 Subject: Initial commit. --- CMakeLists.txt | 8 + README.md | 14 ++ vm/CMakeLists.txt | 19 +++ vm/src/vm.c | 402 +++++++++++++++++++++++++++++++++++++++++++++++++++ vm/src/vm.h | 166 +++++++++++++++++++++ vm/test/test.h | 185 ++++++++++++++++++++++++ vm/test/vm_test.c | 182 +++++++++++++++++++++++ vmrun/CMakeLists.txt | 11 ++ vmrun/src/main.c | 65 +++++++++ 9 files changed, 1052 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 vm/CMakeLists.txt create mode 100644 vm/src/vm.c create mode 100644 vm/src/vm.h create mode 100644 vm/test/test.h create mode 100644 vm/test/vm_test.c create mode 100644 vmrun/CMakeLists.txt create mode 100644 vmrun/src/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..dd251ec --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.0) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED On) +set(CMAKE_C_EXTENSIONS Off) + +add_subdirectory(vm) +add_subdirectory(vmrun) diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f259c4 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# Lang Project + +## VM + +- Add support for type information in a separate stack. +- Add understanding for record types so that we can view and debug rich types. +- Add understanding of functions, or at least labels, so that we can hot-reload + them. + +## VM Runner + +- Text protocol over stdout -> websocketd -> web UI +- Render stack view with type info, VM state view, etc. +- Ability to step through code and view all information. diff --git a/vm/CMakeLists.txt b/vm/CMakeLists.txt new file mode 100644 index 0000000..3199ce6 --- /dev/null +++ b/vm/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.0) + +project(vm) + +# Library +add_library(vm + src/vm.c) + +target_include_directories(vm PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src) + +# Tests +add_executable(vmtest + test/vm_test.c) + +target_link_libraries(vmtest + vm) + +target_compile_options(vmtest PRIVATE -DUNIT_TEST -Wall -Wextra) diff --git a/vm/src/vm.c b/vm/src/vm.c new file mode 100644 index 0000000..559ad5e --- /dev/null +++ b/vm/src/vm.c @@ -0,0 +1,402 @@ +#include "vm.h" + +#include +#include +#include +#include + +// TODO: Make all these arguments of vm_new(). + +// Program's main memory stack size. +#define PROGRAM_STACK_SIZE (64 * 1024) + +// Locals stack size. +#define LOCALS_STACK_SIZE 1024 + +// Frame stack size. +#define FRAME_STACK_SIZE (16 * 1024) + +// Block stack size. +#define BLOCK_STACK_SIZE 1024 + +#define IMPLICIT_LABEL -1 + +// Locals index. +typedef size_t Index; + +// Bools are internally I32s. 0 = false, non-zero = true. +static Type Bool = I32; +typedef int32_t bool_t; + +/// Function frame. +/// +/// Every Frame implicitly starts a Block. The function's locals are inside this +/// implicit block. +typedef struct Frame { + Label label; +} Frame; + +/// A block of code, used for control flow. +/// +/// Blocks have a label that the machine can jump to. Jumps are constrained to +/// the block labels that are in scope. +/// +/// Block termination automatically frees the block's locals. +typedef struct Block { + Label label; + size_t addr; // Address (saved program counter) of the block. + size_t locals_start; // Offset into the locals stack. + size_t locals_size; // Total size in bytes of local variables. +} Block; + +typedef struct Vm { + struct { + bool exit : 1; + } flags; + int32_t exit_code; + size_t pc; // Program instruction counter. + size_t sp; // Program stack pointer. Points to next available slot. + size_t lsp; // Locals stack pointer. Points to next available slot. + size_t fsp; // Frame stack pointer. + size_t bsp; // Block stack pointer. Points to current Block. + uint8_t* stack; // Program stack. Program's main memory. + uint8_t* locals; // Locals stack. Stores locals for each Block. + Frame* frames; // Frame stack for function calls. + Block* blocks; // Block stack for control flow. +} Vm; + +Vm* vm_new() { + Vm* vm = calloc(1, sizeof(Vm)); + if (!vm) { + goto cleanup; + } + if (!(vm->stack = calloc(1, PROGRAM_STACK_SIZE))) { + goto cleanup; + } + if (!(vm->locals = calloc(1, LOCALS_STACK_SIZE))) { + goto cleanup; + } + if (!(vm->frames = calloc(FRAME_STACK_SIZE, sizeof(Frame)))) { + goto cleanup; + } + if (!(vm->blocks = calloc(BLOCK_STACK_SIZE, sizeof(Block)))) { + goto cleanup; + } + return vm; + +cleanup: + vm_del(&vm); + return 0; +} + +void vm_del(Vm** pVm) { + if (pVm && *pVm) { + Vm* vm = *pVm; + if (vm->stack) { + free(vm->stack); + } + if (vm->locals) { + free(vm->locals); + } + if (vm->frames) { + free(vm->frames); + } + if (vm->blocks) { + free(vm->blocks); + } + free(vm); + *pVm = 0; + } +} + +// static size_t get_size(Type type) { +// switch (type) { +// case I32: +// return sizeof(int32_t); +// } +// assert(false); +// return 0; +// } + +// TODO: Not used? +#define VM_ASSERT(expr) \ + {} + +#define TOP(vm, type) \ + (assert(vm->sp >= sizeof(type)), \ + *((const type*)&vm->stack[vm->sp - sizeof(type)])) + +#define PUSH(vm, value, type) \ + assert(vm->sp + sizeof(type) <= PROGRAM_STACK_SIZE); \ + *((type*)(&vm->stack[vm->sp])) = value; \ + vm->sp += sizeof(type); + +#define POP(vm, type) \ + (assert(vm->sp >= sizeof(type)), vm->sp -= sizeof(type), \ + *((type*)(&vm->stack[vm->sp]))) + +#define BLOCK_PUSH(vm, block) \ + assert(vm->bsp < BLOCK_STACK_SIZE); \ + vm->blocks[++vm->bsp] = block; + +#define BLOCK_POP(vm) \ + assert(vm->bsp > 0); \ + vm->locals -= vm->blocks[vm->bsp].locals_size; \ + vm->bsp--; + +#define PUSH_LOCAL(vm, type) \ + assert(vm->lsp + sizeof(type) <= LOCALS_STACK_SIZE); \ + /* Auto-initialize locals to 0. */ \ + *((type*)(&vm->locals[vm->lsp])) = 0; \ + vm->lsp += sizeof(type); \ + vm->blocks[vm->bsp].locals_size += sizeof(type); + +#define POP_LOCAL(vm, type) \ + (assert(vm->lsp >= sizeof(type)), vm->lsp -= sizeof(type), \ + vm->blocks[vm->bsp].locals -= sizeof(type), \ + *((type*)(&vm->locals[vm->lsp]))) + +// TODO: Should be an offset from the current frame, not block. +#define GET_LOCAL_PTR(vm, idx, type) \ + ((const type*)(&vm->locals[vm->blocks[vm->bsp].locals_start + idx])) +// TODO: Same here. +#define GET_LOCAL_PTR_MUT(vm, idx, type) \ + ((type*)(&vm->locals[vm->blocks[vm->bsp].locals_start + idx])) + +#define LOCAL_RD(vm, idx, type) (*GET_LOCAL_PTR(vm, idx, type)) + +#define LOCAL_WR(vm, idx, val, type) (*GET_LOCAL_PTR_MUT(vm, idx, type) = val) + +static void push(Vm* vm, Inst inst) { + switch (inst.type) { + case I32: + PUSH(vm, inst.payload.i32, int32_t); + break; + case F32: + PUSH(vm, inst.payload.f32, float); + break; + } +} + +static Value pop(Vm* vm, Type type) { + Value val; + switch (type) { + case I32: + val.i32 = POP(vm, int32_t); + break; + case F32: + val.f32 = POP(vm, float); + break; + } + return val; +} + +static void vm_exit(Vm* vm, Inst inst) { + vm->exit_code = vm->sp == 0 ? 0 : POP(vm, int32_t); + vm->flags.exit = true; +} + +#define ADD(vm, a, b, field, type) \ + { \ + type result = ((a.field) + (b.field)); \ + PUSH(vm, result, type); \ + } + +static void add(Vm* vm, Type type) { + Value opr1 = pop(vm, type); + Value opr2 = pop(vm, type); + switch (type) { + case I32: + ADD(vm, opr1, opr2, i32, int32_t); + break; + + case F32: + ADD(vm, opr1, opr2, f32, float); + break; + } +} + +static void dec(Vm* vm, Type type) { + Value top = pop(vm, type); + switch (type) { + case I32: + PUSH(vm, top.i32 - 1, int32_t); + break; + case F32: + PUSH(vm, top.f32 - 1.0f, float); + break; + } +} + +static void empty(Vm* vm) { PUSH(vm, vm->sp == 0, bool_t); } + +#define CMP(vm, val, type) (POP(vm, type) == val) + +static void cmp(Vm* vm, Inst inst) { + switch (inst.type) { + case I32: + PUSH(vm, CMP(vm, inst.payload.i32, int32_t), bool_t); + break; + case F32: + PUSH(vm, CMP(vm, inst.payload.f32, float), bool_t); + break; + } +} + +static void end(Vm* vm) { BLOCK_POP(vm); } + +static void loop(Vm* vm, Inst inst) { + const Block block = (Block){.label = inst.payload.i32, .addr = vm->pc}; + BLOCK_PUSH(vm, block); +} + +static void br(Vm* vm, Inst inst) { + const Branch branch = inst.payload.branch; + const int32_t label = branch.label; + const bool value = branch.expected; + const bool is_conditional = branch.conditional; + bool should_branch = is_conditional ? POP(vm, bool_t) == value : true; + // printf("is conditional: %d\n", is_conditional); + // printf("value: %d\n", value); + // printf("should branch: %d\n", should_branch); + if (should_branch) { + while (vm->bsp > 0) { + const Block block = vm->blocks[vm->bsp]; + if (block.label == label) { + vm->pc = block.addr; + vm->pc--; // Account for increment at every step of the VM loop. + return; + } + vm->bsp--; + } + // We should be able to find the label in the block stack. + assert(false); + } +} + +static void vm_break(Vm* vm, Inst inst) { + // TODO. + // Step over instructions until an End instruction is found. +} + +static void local(Vm* vm, Type type) { + switch (type) { + case I32: + PUSH_LOCAL(vm, int32_t); + break; + case F32: + PUSH_LOCAL(vm, float); + break; + } +} + +static void local_rd(Vm* vm, Inst inst) { + const Index idx = (Index)inst.payload.u64; + switch (inst.type) { + case I32: + PUSH(vm, LOCAL_RD(vm, idx, int32_t), int32_t); + break; + case F32: + PUSH(vm, LOCAL_RD(vm, idx, float), float); + break; + } +} + +static void local_wr(Vm* vm, Inst inst) { + const Index idx = (Index)inst.payload.u64; + const Value top = pop(vm, inst.type); + switch (inst.type) { + case I32: + LOCAL_WR(vm, idx, top.i32, int32_t); + break; + case F32: + LOCAL_WR(vm, idx, top.f32, float); + break; + } +} + +static void exec(Vm* vm, Inst inst) { + switch (inst.op) { + case Exit: + vm_exit(vm, inst); + break; + case Push: + push(vm, inst); + break; + case Pop: + pop(vm, inst.type); + break; + case Add: + add(vm, inst.type); + break; + case Sub: + break; + case Mul: + break; + case Div: + break; + case Dec: + dec(vm, inst.type); + break; + case Empty: + empty(vm); + break; + case Cmp: + cmp(vm, inst); + break; + case End: + end(vm); + break; + case Break: + vm_break(vm, inst); + break; + case Loop: + loop(vm, inst); + break; + case Br: + br(vm, inst); + break; + case Func: // TODO + break; + case Arg: // TODO + break; + case Call: // TODO + break; + case Local: + local(vm, inst.type); + break; + case LocalRd: + local_rd(vm, inst); + break; + case LocalWr: + local_wr(vm, inst); + break; + } +} + +static void init(Vm* vm) { + // Create an implicit frame for the start of the program. + vm->frames[0] = (Frame){.label = IMPLICIT_LABEL}; + vm->blocks[0] = (Block){.label = IMPLICIT_LABEL}; + // TODO: Reset all Vm state. +} + +int vm_run(Vm* vm, const Inst instructions[], size_t count) { + assert(vm); + init(vm); + for (vm->pc = 0; !vm->flags.exit && vm->pc < count; vm->pc++) { + const Inst inst = instructions[vm->pc]; + exec(vm, inst); + } + // printf("exit code: %d\n", vm->exit_code); + return vm->exit_code; +} + +void vm_print_stack(const Vm* vm) { + printf("stack start\n"); + for (size_t i = 0; i < vm->sp; ++i) { + const char sep = (i == vm->sp - 1) ? '\n' : ' '; + printf("%x%c", vm->stack[i], sep); + } + printf("stack end\n"); +} diff --git a/vm/src/vm.h b/vm/src/vm.h new file mode 100644 index 0000000..03dfc88 --- /dev/null +++ b/vm/src/vm.h @@ -0,0 +1,166 @@ +#pragma once + +#include +#include +#include + +typedef enum Op { + Exit, // Pop value from the stack and return as exit code. Return 0 if the + // stack is empty. + Push, + Pop, + Add, + Sub, + Mul, + Div, + Dec, // Decrement the top of the stack by 1. + Empty, // Check whether the stack is empty. Pushes a bool. + Cmp, // Pop the top of the stack and compare it with the payload. Pushes a + // bool. + /* Blocks */ + End, // Marks the end of a block. + Break, // Exit the current block. + Loop, // Push a loop block. Payload (i32): label. + /* Branches */ + Br, // Branch. Payload (i64): [(i32) conditional? | (i32) label]. + // A condtional branch pops a bool from the stack and branches if true. + // The condition can also be negated. See br_if(). + /* Functions */ + Func, + Arg, + Call, + /* Locals */ + Local, // Create a local variable. + LocalRd, // Load a local variable into the top of the stack. + LocalWr, // Pop the top of the stack and store it in a local variable. +} Op; + +typedef enum Type { + I32, + F32, +} Type; + +// Label type for blocks and locals. +typedef uint32_t Label; + +typedef struct Branch { + Label label; + bool conditional : 1; // True for conditional branches. + bool expected : 1; // Comparison value for conditional branches. +} Branch; + +typedef struct Function { + Label label; +} Function; + +typedef struct Value { + union { + uint64_t u64; + int32_t i32; + float f32; + Branch branch; + Label label; + }; +} Value; + +typedef struct Inst { + Op op : 5; + Type type : 2; + Value payload; +} Inst; + +typedef struct Vm Vm; + +// ----------------------------------------------------------------------------- +// VM API + +/// Create a new virtual machine. +Vm* vm_new(); + +/// Destroy the virtual machine. +void vm_del(Vm**); + +/// Execute code on the virtual machine. +/// +/// Returns the program exit code if an exit operation is executed, 0 otherwise. +int vm_run(Vm*, const Inst[], size_t count); + +/// Prints the virtual machine's stack to stdout. +void vm_print_stack(const Vm*); + +// ----------------------------------------------------------------------------- +// Programming API + +/// Exit the program. +static inline Inst vmExit() { return (Inst){.op = Exit}; } + +/// Push a value. +static inline Inst vmPushI32(int32_t value) { + return (Inst){.op = Push, .type = I32, .payload = (Value){.i32 = value}}; +} + +/// Pop a value. +static inline Inst vmPop(Type type) { return (Inst){.op = Pop, .type = type}; } + +/// Add two values. +static inline Inst vmAdd(Type type) { return (Inst){.op = Add, .type = type}; } + +/// Decrement a value. +static inline Inst vmDec(Type type) { return (Inst){.op = Dec, .type = type}; } + +/// Compare a value. +static inline Inst vmCmpI32(int32_t value) { + return (Inst){.op = Cmp, .type = I32, .payload = (Value){.i32 = value}}; +} + +/// End the current block. +static inline Inst vmEnd() { return (Inst){.op = End}; } + +/// Create a loop. +static inline Inst vmLoop(Label label) { + return (Inst){.op = Loop, .payload = (Value){.label = label}}; +} + +/// Create the payload of a conditional branch. +static inline Inst vmBr_if(bool value, Label label) { + return (Inst){ + .op = Br, + .payload = (Value){ + .branch = { + .label = label, + .conditional = 1, + .expected = value, + }}}; +} + +/// Create a function. +static inline Inst vmFunc(Label label) { + return (Inst){.op = Func, .payload = (Value){.label = label}}; +} + +/// Create a function argument. +static inline Inst vmArg(Type type, Label label) { + return (Inst){.op = Arg, .type = type, .payload = (Value){.label = label}}; +} + +/// Call a function. +static inline Inst vmCall(Label label) { + return (Inst){.op = Call, .payload = (Value){.label = label}}; +} + +/// Create a local variable. +static inline Inst vmLocal(Type type, Label label) { + return (Inst){.op = Local, .type = type, .payload = (Value){.label = label}}; +} + +/// Read a local variable. +static inline Inst vmLocalRd(Type type, Label label) { + return (Inst){ + .op = LocalRd, .type = type, .payload = (Value){.label = label}}; +} + +/// Write a local variable. +static inline Inst vmLocalWr(Type type, Label label) { + return (Inst){ + .op = LocalWr, .type = type, .payload = (Value){.label = label}}; +} diff --git a/vm/test/test.h b/vm/test/test.h new file mode 100644 index 0000000..fd8dc22 --- /dev/null +++ b/vm/test/test.h @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MIT +#pragma once + +#ifdef UNIT_TEST + +#include +#include +#include +#include + +#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \ + defined(__NetBSD__) || defined(__OpenBSD__) +#define USE_SYSCTL_FOR_ARGS 1 +// clang-format off +#include +#include +// clang-format on +#include // getpid +#endif + +struct test_file_metadata; + +struct test_failure { + bool present; + const char *message; + const char *file; + int line; +}; + +struct test_case_metadata { + void (*fn)(struct test_case_metadata *, struct test_file_metadata *); + struct test_failure failure; + const char *name; + struct test_case_metadata *next; +}; + +struct test_file_metadata { + bool registered; + const char *name; + struct test_file_metadata *next; + struct test_case_metadata *tests; +}; + +struct test_file_metadata __attribute__((weak)) * test_file_head; + +#define SET_FAILURE(_message) \ + metadata->failure = (struct test_failure) { \ + .message = _message, .file = __FILE__, .line = __LINE__, .present = true, \ + } + +#define TEST_EQUAL(a, b) \ + do { \ + if ((a) != (b)) { \ + SET_FAILURE(#a " != " #b); \ + return; \ + } \ + } while (0) + +#define TEST_TRUE(a) \ + do { \ + if (!(a)) { \ + SET_FAILURE(#a " is not true"); \ + return; \ + } \ + } while (0) + +#define TEST_STREQUAL(a, b) \ + do { \ + if (strcmp(a, b) != 0) { \ + SET_FAILURE(#a " != " #b); \ + return; \ + } \ + } while (0) + +#define TEST_CASE(_name) \ + static void __test_h_##_name(struct test_case_metadata *, \ + struct test_file_metadata *); \ + static struct test_file_metadata __test_h_file; \ + static struct test_case_metadata __test_h_meta_##_name = { \ + .name = #_name, \ + .fn = __test_h_##_name, \ + }; \ + static void __attribute__((constructor(101))) __test_h_##_name##_register(void) { \ + __test_h_meta_##_name.next = __test_h_file.tests; \ + __test_h_file.tests = &__test_h_meta_##_name; \ + if (!__test_h_file.registered) { \ + __test_h_file.name = __FILE__; \ + __test_h_file.next = test_file_head; \ + test_file_head = &__test_h_file; \ + __test_h_file.registered = true; \ + } \ + } \ + static void __test_h_##_name( \ + struct test_case_metadata *metadata __attribute__((unused)), \ + struct test_file_metadata *file_metadata __attribute__((unused))) + +extern void __attribute__((weak)) (*test_h_unittest_setup)(void); +/// Run defined tests, return true if all tests succeeds +/// @param[out] tests_run if not NULL, set to whether tests were run +static inline void __attribute__((constructor(102))) run_tests(void) { + bool should_run = false; +#ifdef USE_SYSCTL_FOR_ARGS + int mib[] = { + CTL_KERN, +#if defined(__NetBSD__) || defined(__OpenBSD__) + KERN_PROC_ARGS, + getpid(), + KERN_PROC_ARGV, +#else + KERN_PROC, + KERN_PROC_ARGS, + getpid(), +#endif + }; + char *arg = NULL; + size_t arglen; + sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &arglen, NULL, 0); + arg = malloc(arglen); + sysctl(mib, sizeof(mib) / sizeof(mib[0]), arg, &arglen, NULL, 0); +#else + FILE *cmdlinef = fopen("/proc/self/cmdline", "r"); + char *arg = NULL; + int arglen; + fscanf(cmdlinef, "%ms%n", &arg, &arglen); + fclose(cmdlinef); +#endif + for (char *pos = arg; pos < arg + arglen; pos += strlen(pos) + 1) { + if (strcmp(pos, "--unittest") == 0) { + should_run = true; + break; + } + } + free(arg); + + if (!should_run) { + return; + } + + if (&test_h_unittest_setup) { + test_h_unittest_setup(); + } + + struct test_file_metadata *i = test_file_head; + int failed = 0, success = 0; + while (i) { + fprintf(stderr, "Running tests from %s:\n", i->name); + struct test_case_metadata *j = i->tests; + while (j) { + fprintf(stderr, "\t%s ... ", j->name); + j->failure.present = false; + j->fn(j, i); + if (j->failure.present) { + fprintf(stderr, "failed (%s at %s:%d)\n", j->failure.message, + j->failure.file, j->failure.line); + failed++; + } else { + fprintf(stderr, "passed\n"); + success++; + } + j = j->next; + } + fprintf(stderr, "\n"); + i = i->next; + } + int total = failed + success; + fprintf(stderr, "Test results: passed %d/%d, failed %d/%d\n", success, total, + failed, total); + exit(failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE); +} + +#else + +#include + +#define TEST_CASE(name) static void __attribute__((unused)) __test_h_##name(void) + +#define TEST_EQUAL(a, b) \ + (void)(a); \ + (void)(b) +#define TEST_TRUE(a) (void)(a) +#define TEST_STREQUAL(a, b) \ + (void)(a); \ + (void)(b) + +#endif diff --git a/vm/test/vm_test.c b/vm/test/vm_test.c new file mode 100644 index 0000000..2d1a91f --- /dev/null +++ b/vm/test/vm_test.c @@ -0,0 +1,182 @@ +#include "vm.h" + +#include "test.h" + +#include + +/// Create and destroy a vm. +TEST_CASE(vm_create_destroy) { + Vm* vm = vm_new(); + TEST_TRUE(vm != 0); + vm_del(&vm); +} + +// Exit with an implicit 0 exit code. +TEST_CASE(vm_exit_implicit) { + // clang-format off + const Inst instructions[] = { + vmExit(), + }; + // clang-format on + + Vm* vm = vm_new(); + TEST_TRUE(vm != 0); + const int exit_code = + vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst)); + TEST_TRUE(exit_code == 0); + vm_del(&vm); +} + +// Exit with an explicit exit code. +TEST_CASE(vm_exit_explicit) { + const int32_t expected = 17; + + // clang-format off + const Inst instructions[] = { + vmPushI32(expected), + vmExit(), + }; + // clang-format on + + Vm* vm = vm_new(); + TEST_TRUE(vm != 0); + const int exit_code = + vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst)); + TEST_TRUE(exit_code == expected); + vm_del(&vm); +} + +/// Add two i32 numbers. +TEST_CASE(vm_add_i32) { + const int n1 = 2; + const int n2 = 3; + + // clang-format off + const Inst instructions[] = { + vmPushI32(n1), + vmPushI32(n2), + vmAdd(I32), + vmExit(), + }; + // clang-format on + + Vm* vm = vm_new(); + TEST_TRUE(vm != 0); + const int exit_code = + vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst)); + TEST_EQUAL(exit_code, n1 + n2); + vm_del(&vm); +} + +/// Sum an array of numbers with 4 add instructions. +TEST_CASE(vm_sum_array_i32_explicit) { + const int vals[5] = {1, 2, 3, 4, 5}; + + // clang-format off + const Inst instructions[] = { + vmPushI32(vals[0]), + vmPushI32(vals[1]), + vmPushI32(vals[2]), + vmPushI32(vals[3]), + vmPushI32(vals[4]), + vmAdd(I32), + vmAdd(I32), + vmAdd(I32), + vmAdd(I32), + vmExit(), + }; + // clang-format on + + int sum = 0; + for (size_t i = 0; i < sizeof(vals) / sizeof(vals[0]); ++i) { + sum += vals[i]; + } + + Vm* vm = vm_new(); + TEST_TRUE(vm != 0); + const int exit_code = + vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst)); + TEST_EQUAL(exit_code, sum); + vm_del(&vm); +} + +/// Sum an array of numbers with a loop. +TEST_CASE(vm_sum_array_i32_loop) { + const int vals[5] = {1, 2, 3, 4, 5}; + + const Label loop_label = 0; + const Label counter_index = 0; + + // clang-format off + const Inst instructions[] = { + vmPushI32(vals[0]), + vmPushI32(vals[1]), + vmPushI32(vals[2]), + vmPushI32(vals[3]), + vmPushI32(vals[4]), + vmLocal(I32, counter_index), + vmPushI32(sizeof(vals) / sizeof(vals[0]) - 1), + vmLocalWr(I32, counter_index), + vmLoop(loop_label), + vmAdd(I32), + vmLocalRd(I32, counter_index), + vmDec(I32), + // TODO: Could be useful to have a function that writes the local but + // leaves its value on the stack. + vmLocalWr(I32, counter_index), + vmLocalRd(I32, counter_index), + // TODO: Perhaps we should expect the comparison value to also be pushed + // to the stack. + vmCmpI32(0), + vmBr_if(false, loop_label), + vmEnd(), + vmExit(), + }; + // clang-format on + + int sum = 0; + for (size_t i = 0; i < sizeof(vals) / sizeof(vals[0]); ++i) { + sum += vals[i]; + } + + Vm* vm = vm_new(); + TEST_TRUE(vm != 0); + const int exit_code = + vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst)); + TEST_EQUAL(exit_code, sum); + vm_del(&vm); +} + +// Call a function to add two numbers. +TEST_CASE(vm_function_call) { + const Label func = 0; + const Label a = 0; + const Label b = 1; + const int32_t a_val = 3; + const int32_t b_val = 5; + const int32_t expected = a + b; + + // clang-format off + const Inst instructions[] = { + /* Function definition */ + vmFunc(func), + vmArg(I32, b), + vmArg(I32, a), + vmAdd(I32), + vmEnd(), + /* Main program */ + vmPushI32(a_val), + vmPushI32(b_val), + vmCall(func), + vmExit(), + }; + // clang-format on + + Vm* vm = vm_new(); + TEST_TRUE(vm != 0); + // const int exit_code = + // vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst)); + vm_del(&vm); +} + +int main() { return 0; } diff --git a/vmrun/CMakeLists.txt b/vmrun/CMakeLists.txt new file mode 100644 index 0000000..1a81995 --- /dev/null +++ b/vmrun/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.0) + +project(vmrun) + +add_executable(vmrun + src/main.c) + +target_link_libraries(vmrun + vm) + +target_compile_options(vmrun PRIVATE -Wall -Wextra) diff --git a/vmrun/src/main.c b/vmrun/src/main.c new file mode 100644 index 0000000..0bdd16f --- /dev/null +++ b/vmrun/src/main.c @@ -0,0 +1,65 @@ +#include + +#include +#include +#include + +typedef enum CommandType { + ExitSession, + PrintStack, +} CommandType; + +typedef struct Command { + CommandType type; +} Command; + +static bool read_command(char line[], Command* pCmd) { + static const char* delim = " \n"; + + const char* cmd = strtok(line, delim); + if (strcmp(cmd, "exit") == 0) { + *pCmd = (Command){.type = ExitSession}; + return true; + } else if (strcmp(cmd, "print_stack") == 0) { + *pCmd = (Command){.type = PrintStack}; + return true; + } + return false; +} + +/// Runs a command. +/// +/// Returns true unless on ExitSession. +static bool run_command(Vm* vm, Command cmd) { + switch (cmd.type) { + case ExitSession: + return false; + case PrintStack: + vm_print_stack(vm); + break; + } + return true; +} + +int main() { + Vm* vm = vm_new(); + if (!vm) { + fprintf(stderr, "Failed to create VM\n"); + return 1; + } + + Command cmd; + bool should_continue = true; + char line[128]; + + while (should_continue && fgets(line, sizeof(line), stdin)) { + if (read_command(line, &cmd)) { + should_continue = run_command(vm, cmd); + } else { + fprintf(stderr, "Unknown command\n"); + } + } + + vm_del(&vm); + return 0; +} -- cgit v1.2.3