diff options
| author | 3gg <3gg@shellblade.net> | 2023-03-02 20:03:52 -0800 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2023-03-02 20:03:52 -0800 |
| commit | 664006b1c42aae84a3c749d9b71c1047e0b8ffcf (patch) | |
| tree | e08f8af944b132742b3bb1d240d8954328e667e5 | |
Initial commit.
| -rw-r--r-- | CMakeLists.txt | 8 | ||||
| -rw-r--r-- | README.md | 14 | ||||
| -rw-r--r-- | vm/CMakeLists.txt | 19 | ||||
| -rw-r--r-- | vm/src/vm.c | 402 | ||||
| -rw-r--r-- | vm/src/vm.h | 166 | ||||
| -rw-r--r-- | vm/test/test.h | 185 | ||||
| -rw-r--r-- | vm/test/vm_test.c | 182 | ||||
| -rw-r--r-- | vmrun/CMakeLists.txt | 11 | ||||
| -rw-r--r-- | vmrun/src/main.c | 65 |
9 files changed, 1052 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..dd251ec --- /dev/null +++ b/CMakeLists.txt | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | cmake_minimum_required(VERSION 3.0) | ||
| 2 | |||
| 3 | set(CMAKE_C_STANDARD 11) | ||
| 4 | set(CMAKE_C_STANDARD_REQUIRED On) | ||
| 5 | set(CMAKE_C_EXTENSIONS Off) | ||
| 6 | |||
| 7 | add_subdirectory(vm) | ||
| 8 | 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 @@ | |||
| 1 | # Lang Project | ||
| 2 | |||
| 3 | ## VM | ||
| 4 | |||
| 5 | - Add support for type information in a separate stack. | ||
| 6 | - Add understanding for record types so that we can view and debug rich types. | ||
| 7 | - Add understanding of functions, or at least labels, so that we can hot-reload | ||
| 8 | them. | ||
| 9 | |||
| 10 | ## VM Runner | ||
| 11 | |||
| 12 | - Text protocol over stdout -> websocketd -> web UI | ||
| 13 | - Render stack view with type info, VM state view, etc. | ||
| 14 | - 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 @@ | |||
| 1 | cmake_minimum_required(VERSION 3.0) | ||
| 2 | |||
| 3 | project(vm) | ||
| 4 | |||
| 5 | # Library | ||
| 6 | add_library(vm | ||
| 7 | src/vm.c) | ||
| 8 | |||
| 9 | target_include_directories(vm PUBLIC | ||
| 10 | ${CMAKE_CURRENT_SOURCE_DIR}/src) | ||
| 11 | |||
| 12 | # Tests | ||
| 13 | add_executable(vmtest | ||
| 14 | test/vm_test.c) | ||
| 15 | |||
| 16 | target_link_libraries(vmtest | ||
| 17 | vm) | ||
| 18 | |||
| 19 | 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 @@ | |||
| 1 | #include "vm.h" | ||
| 2 | |||
| 3 | #include <assert.h> | ||
| 4 | #include <stdbool.h> | ||
| 5 | #include <stdio.h> | ||
| 6 | #include <stdlib.h> | ||
| 7 | |||
| 8 | // TODO: Make all these arguments of vm_new(). | ||
| 9 | |||
| 10 | // Program's main memory stack size. | ||
| 11 | #define PROGRAM_STACK_SIZE (64 * 1024) | ||
| 12 | |||
| 13 | // Locals stack size. | ||
| 14 | #define LOCALS_STACK_SIZE 1024 | ||
| 15 | |||
| 16 | // Frame stack size. | ||
| 17 | #define FRAME_STACK_SIZE (16 * 1024) | ||
| 18 | |||
| 19 | // Block stack size. | ||
| 20 | #define BLOCK_STACK_SIZE 1024 | ||
| 21 | |||
| 22 | #define IMPLICIT_LABEL -1 | ||
| 23 | |||
| 24 | // Locals index. | ||
| 25 | typedef size_t Index; | ||
| 26 | |||
| 27 | // Bools are internally I32s. 0 = false, non-zero = true. | ||
| 28 | static Type Bool = I32; | ||
| 29 | typedef int32_t bool_t; | ||
| 30 | |||
| 31 | /// Function frame. | ||
| 32 | /// | ||
| 33 | /// Every Frame implicitly starts a Block. The function's locals are inside this | ||
| 34 | /// implicit block. | ||
| 35 | typedef struct Frame { | ||
| 36 | Label label; | ||
| 37 | } Frame; | ||
| 38 | |||
| 39 | /// A block of code, used for control flow. | ||
| 40 | /// | ||
| 41 | /// Blocks have a label that the machine can jump to. Jumps are constrained to | ||
| 42 | /// the block labels that are in scope. | ||
| 43 | /// | ||
| 44 | /// Block termination automatically frees the block's locals. | ||
| 45 | typedef struct Block { | ||
| 46 | Label label; | ||
| 47 | size_t addr; // Address (saved program counter) of the block. | ||
| 48 | size_t locals_start; // Offset into the locals stack. | ||
| 49 | size_t locals_size; // Total size in bytes of local variables. | ||
| 50 | } Block; | ||
| 51 | |||
| 52 | typedef struct Vm { | ||
| 53 | struct { | ||
| 54 | bool exit : 1; | ||
| 55 | } flags; | ||
| 56 | int32_t exit_code; | ||
| 57 | size_t pc; // Program instruction counter. | ||
| 58 | size_t sp; // Program stack pointer. Points to next available slot. | ||
| 59 | size_t lsp; // Locals stack pointer. Points to next available slot. | ||
| 60 | size_t fsp; // Frame stack pointer. | ||
| 61 | size_t bsp; // Block stack pointer. Points to current Block. | ||
| 62 | uint8_t* stack; // Program stack. Program's main memory. | ||
| 63 | uint8_t* locals; // Locals stack. Stores locals for each Block. | ||
| 64 | Frame* frames; // Frame stack for function calls. | ||
| 65 | Block* blocks; // Block stack for control flow. | ||
| 66 | } Vm; | ||
| 67 | |||
| 68 | Vm* vm_new() { | ||
| 69 | Vm* vm = calloc(1, sizeof(Vm)); | ||
| 70 | if (!vm) { | ||
| 71 | goto cleanup; | ||
| 72 | } | ||
| 73 | if (!(vm->stack = calloc(1, PROGRAM_STACK_SIZE))) { | ||
| 74 | goto cleanup; | ||
| 75 | } | ||
| 76 | if (!(vm->locals = calloc(1, LOCALS_STACK_SIZE))) { | ||
| 77 | goto cleanup; | ||
| 78 | } | ||
| 79 | if (!(vm->frames = calloc(FRAME_STACK_SIZE, sizeof(Frame)))) { | ||
| 80 | goto cleanup; | ||
| 81 | } | ||
| 82 | if (!(vm->blocks = calloc(BLOCK_STACK_SIZE, sizeof(Block)))) { | ||
| 83 | goto cleanup; | ||
| 84 | } | ||
| 85 | return vm; | ||
| 86 | |||
| 87 | cleanup: | ||
| 88 | vm_del(&vm); | ||
| 89 | return 0; | ||
| 90 | } | ||
| 91 | |||
| 92 | void vm_del(Vm** pVm) { | ||
| 93 | if (pVm && *pVm) { | ||
| 94 | Vm* vm = *pVm; | ||
| 95 | if (vm->stack) { | ||
| 96 | free(vm->stack); | ||
| 97 | } | ||
| 98 | if (vm->locals) { | ||
| 99 | free(vm->locals); | ||
| 100 | } | ||
| 101 | if (vm->frames) { | ||
| 102 | free(vm->frames); | ||
| 103 | } | ||
| 104 | if (vm->blocks) { | ||
| 105 | free(vm->blocks); | ||
| 106 | } | ||
| 107 | free(vm); | ||
| 108 | *pVm = 0; | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | // static size_t get_size(Type type) { | ||
| 113 | // switch (type) { | ||
| 114 | // case I32: | ||
| 115 | // return sizeof(int32_t); | ||
| 116 | // } | ||
| 117 | // assert(false); | ||
| 118 | // return 0; | ||
| 119 | // } | ||
| 120 | |||
| 121 | // TODO: Not used? | ||
| 122 | #define VM_ASSERT(expr) \ | ||
| 123 | {} | ||
| 124 | |||
| 125 | #define TOP(vm, type) \ | ||
| 126 | (assert(vm->sp >= sizeof(type)), \ | ||
| 127 | *((const type*)&vm->stack[vm->sp - sizeof(type)])) | ||
| 128 | |||
| 129 | #define PUSH(vm, value, type) \ | ||
| 130 | assert(vm->sp + sizeof(type) <= PROGRAM_STACK_SIZE); \ | ||
| 131 | *((type*)(&vm->stack[vm->sp])) = value; \ | ||
| 132 | vm->sp += sizeof(type); | ||
| 133 | |||
| 134 | #define POP(vm, type) \ | ||
| 135 | (assert(vm->sp >= sizeof(type)), vm->sp -= sizeof(type), \ | ||
| 136 | *((type*)(&vm->stack[vm->sp]))) | ||
| 137 | |||
| 138 | #define BLOCK_PUSH(vm, block) \ | ||
| 139 | assert(vm->bsp < BLOCK_STACK_SIZE); \ | ||
| 140 | vm->blocks[++vm->bsp] = block; | ||
| 141 | |||
| 142 | #define BLOCK_POP(vm) \ | ||
| 143 | assert(vm->bsp > 0); \ | ||
| 144 | vm->locals -= vm->blocks[vm->bsp].locals_size; \ | ||
| 145 | vm->bsp--; | ||
| 146 | |||
| 147 | #define PUSH_LOCAL(vm, type) \ | ||
| 148 | assert(vm->lsp + sizeof(type) <= LOCALS_STACK_SIZE); \ | ||
| 149 | /* Auto-initialize locals to 0. */ \ | ||
| 150 | *((type*)(&vm->locals[vm->lsp])) = 0; \ | ||
| 151 | vm->lsp += sizeof(type); \ | ||
| 152 | vm->blocks[vm->bsp].locals_size += sizeof(type); | ||
| 153 | |||
| 154 | #define POP_LOCAL(vm, type) \ | ||
| 155 | (assert(vm->lsp >= sizeof(type)), vm->lsp -= sizeof(type), \ | ||
| 156 | vm->blocks[vm->bsp].locals -= sizeof(type), \ | ||
| 157 | *((type*)(&vm->locals[vm->lsp]))) | ||
| 158 | |||
| 159 | // TODO: Should be an offset from the current frame, not block. | ||
| 160 | #define GET_LOCAL_PTR(vm, idx, type) \ | ||
| 161 | ((const type*)(&vm->locals[vm->blocks[vm->bsp].locals_start + idx])) | ||
| 162 | // TODO: Same here. | ||
| 163 | #define GET_LOCAL_PTR_MUT(vm, idx, type) \ | ||
| 164 | ((type*)(&vm->locals[vm->blocks[vm->bsp].locals_start + idx])) | ||
| 165 | |||
| 166 | #define LOCAL_RD(vm, idx, type) (*GET_LOCAL_PTR(vm, idx, type)) | ||
| 167 | |||
| 168 | #define LOCAL_WR(vm, idx, val, type) (*GET_LOCAL_PTR_MUT(vm, idx, type) = val) | ||
| 169 | |||
| 170 | static void push(Vm* vm, Inst inst) { | ||
| 171 | switch (inst.type) { | ||
| 172 | case I32: | ||
| 173 | PUSH(vm, inst.payload.i32, int32_t); | ||
| 174 | break; | ||
| 175 | case F32: | ||
| 176 | PUSH(vm, inst.payload.f32, float); | ||
| 177 | break; | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | static Value pop(Vm* vm, Type type) { | ||
| 182 | Value val; | ||
| 183 | switch (type) { | ||
| 184 | case I32: | ||
| 185 | val.i32 = POP(vm, int32_t); | ||
| 186 | break; | ||
| 187 | case F32: | ||
| 188 | val.f32 = POP(vm, float); | ||
| 189 | break; | ||
| 190 | } | ||
| 191 | return val; | ||
| 192 | } | ||
| 193 | |||
| 194 | static void vm_exit(Vm* vm, Inst inst) { | ||
| 195 | vm->exit_code = vm->sp == 0 ? 0 : POP(vm, int32_t); | ||
| 196 | vm->flags.exit = true; | ||
| 197 | } | ||
| 198 | |||
| 199 | #define ADD(vm, a, b, field, type) \ | ||
| 200 | { \ | ||
| 201 | type result = ((a.field) + (b.field)); \ | ||
| 202 | PUSH(vm, result, type); \ | ||
| 203 | } | ||
| 204 | |||
| 205 | static void add(Vm* vm, Type type) { | ||
| 206 | Value opr1 = pop(vm, type); | ||
| 207 | Value opr2 = pop(vm, type); | ||
| 208 | switch (type) { | ||
| 209 | case I32: | ||
| 210 | ADD(vm, opr1, opr2, i32, int32_t); | ||
| 211 | break; | ||
| 212 | |||
| 213 | case F32: | ||
| 214 | ADD(vm, opr1, opr2, f32, float); | ||
| 215 | break; | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | static void dec(Vm* vm, Type type) { | ||
| 220 | Value top = pop(vm, type); | ||
| 221 | switch (type) { | ||
| 222 | case I32: | ||
| 223 | PUSH(vm, top.i32 - 1, int32_t); | ||
| 224 | break; | ||
| 225 | case F32: | ||
| 226 | PUSH(vm, top.f32 - 1.0f, float); | ||
| 227 | break; | ||
| 228 | } | ||
| 229 | } | ||
| 230 | |||
| 231 | static void empty(Vm* vm) { PUSH(vm, vm->sp == 0, bool_t); } | ||
| 232 | |||
| 233 | #define CMP(vm, val, type) (POP(vm, type) == val) | ||
| 234 | |||
| 235 | static void cmp(Vm* vm, Inst inst) { | ||
| 236 | switch (inst.type) { | ||
| 237 | case I32: | ||
| 238 | PUSH(vm, CMP(vm, inst.payload.i32, int32_t), bool_t); | ||
| 239 | break; | ||
| 240 | case F32: | ||
| 241 | PUSH(vm, CMP(vm, inst.payload.f32, float), bool_t); | ||
| 242 | break; | ||
| 243 | } | ||
| 244 | } | ||
| 245 | |||
| 246 | static void end(Vm* vm) { BLOCK_POP(vm); } | ||
| 247 | |||
| 248 | static void loop(Vm* vm, Inst inst) { | ||
| 249 | const Block block = (Block){.label = inst.payload.i32, .addr = vm->pc}; | ||
| 250 | BLOCK_PUSH(vm, block); | ||
| 251 | } | ||
| 252 | |||
| 253 | static void br(Vm* vm, Inst inst) { | ||
| 254 | const Branch branch = inst.payload.branch; | ||
| 255 | const int32_t label = branch.label; | ||
| 256 | const bool value = branch.expected; | ||
| 257 | const bool is_conditional = branch.conditional; | ||
| 258 | bool should_branch = is_conditional ? POP(vm, bool_t) == value : true; | ||
| 259 | // printf("is conditional: %d\n", is_conditional); | ||
| 260 | // printf("value: %d\n", value); | ||
| 261 | // printf("should branch: %d\n", should_branch); | ||
| 262 | if (should_branch) { | ||
| 263 | while (vm->bsp > 0) { | ||
| 264 | const Block block = vm->blocks[vm->bsp]; | ||
| 265 | if (block.label == label) { | ||
| 266 | vm->pc = block.addr; | ||
| 267 | vm->pc--; // Account for increment at every step of the VM loop. | ||
| 268 | return; | ||
| 269 | } | ||
| 270 | vm->bsp--; | ||
| 271 | } | ||
| 272 | // We should be able to find the label in the block stack. | ||
| 273 | assert(false); | ||
| 274 | } | ||
| 275 | } | ||
| 276 | |||
| 277 | static void vm_break(Vm* vm, Inst inst) { | ||
| 278 | // TODO. | ||
| 279 | // Step over instructions until an End instruction is found. | ||
| 280 | } | ||
| 281 | |||
| 282 | static void local(Vm* vm, Type type) { | ||
| 283 | switch (type) { | ||
| 284 | case I32: | ||
| 285 | PUSH_LOCAL(vm, int32_t); | ||
| 286 | break; | ||
| 287 | case F32: | ||
| 288 | PUSH_LOCAL(vm, float); | ||
| 289 | break; | ||
| 290 | } | ||
| 291 | } | ||
| 292 | |||
| 293 | static void local_rd(Vm* vm, Inst inst) { | ||
| 294 | const Index idx = (Index)inst.payload.u64; | ||
| 295 | switch (inst.type) { | ||
| 296 | case I32: | ||
| 297 | PUSH(vm, LOCAL_RD(vm, idx, int32_t), int32_t); | ||
| 298 | break; | ||
| 299 | case F32: | ||
| 300 | PUSH(vm, LOCAL_RD(vm, idx, float), float); | ||
| 301 | break; | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | static void local_wr(Vm* vm, Inst inst) { | ||
| 306 | const Index idx = (Index)inst.payload.u64; | ||
| 307 | const Value top = pop(vm, inst.type); | ||
| 308 | switch (inst.type) { | ||
| 309 | case I32: | ||
| 310 | LOCAL_WR(vm, idx, top.i32, int32_t); | ||
| 311 | break; | ||
| 312 | case F32: | ||
| 313 | LOCAL_WR(vm, idx, top.f32, float); | ||
| 314 | break; | ||
| 315 | } | ||
| 316 | } | ||
| 317 | |||
| 318 | static void exec(Vm* vm, Inst inst) { | ||
| 319 | switch (inst.op) { | ||
| 320 | case Exit: | ||
| 321 | vm_exit(vm, inst); | ||
| 322 | break; | ||
| 323 | case Push: | ||
| 324 | push(vm, inst); | ||
| 325 | break; | ||
| 326 | case Pop: | ||
| 327 | pop(vm, inst.type); | ||
| 328 | break; | ||
| 329 | case Add: | ||
| 330 | add(vm, inst.type); | ||
| 331 | break; | ||
| 332 | case Sub: | ||
| 333 | break; | ||
| 334 | case Mul: | ||
| 335 | break; | ||
| 336 | case Div: | ||
| 337 | break; | ||
| 338 | case Dec: | ||
| 339 | dec(vm, inst.type); | ||
| 340 | break; | ||
| 341 | case Empty: | ||
| 342 | empty(vm); | ||
| 343 | break; | ||
| 344 | case Cmp: | ||
| 345 | cmp(vm, inst); | ||
| 346 | break; | ||
| 347 | case End: | ||
| 348 | end(vm); | ||
| 349 | break; | ||
| 350 | case Break: | ||
| 351 | vm_break(vm, inst); | ||
| 352 | break; | ||
| 353 | case Loop: | ||
| 354 | loop(vm, inst); | ||
| 355 | break; | ||
| 356 | case Br: | ||
| 357 | br(vm, inst); | ||
| 358 | break; | ||
| 359 | case Func: // TODO | ||
| 360 | break; | ||
| 361 | case Arg: // TODO | ||
| 362 | break; | ||
| 363 | case Call: // TODO | ||
| 364 | break; | ||
| 365 | case Local: | ||
| 366 | local(vm, inst.type); | ||
| 367 | break; | ||
| 368 | case LocalRd: | ||
| 369 | local_rd(vm, inst); | ||
| 370 | break; | ||
| 371 | case LocalWr: | ||
| 372 | local_wr(vm, inst); | ||
| 373 | break; | ||
| 374 | } | ||
| 375 | } | ||
| 376 | |||
| 377 | static void init(Vm* vm) { | ||
| 378 | // Create an implicit frame for the start of the program. | ||
| 379 | vm->frames[0] = (Frame){.label = IMPLICIT_LABEL}; | ||
| 380 | vm->blocks[0] = (Block){.label = IMPLICIT_LABEL}; | ||
| 381 | // TODO: Reset all Vm state. | ||
| 382 | } | ||
| 383 | |||
| 384 | int vm_run(Vm* vm, const Inst instructions[], size_t count) { | ||
| 385 | assert(vm); | ||
| 386 | init(vm); | ||
| 387 | for (vm->pc = 0; !vm->flags.exit && vm->pc < count; vm->pc++) { | ||
| 388 | const Inst inst = instructions[vm->pc]; | ||
| 389 | exec(vm, inst); | ||
| 390 | } | ||
| 391 | // printf("exit code: %d\n", vm->exit_code); | ||
| 392 | return vm->exit_code; | ||
| 393 | } | ||
| 394 | |||
| 395 | void vm_print_stack(const Vm* vm) { | ||
| 396 | printf("stack start\n"); | ||
| 397 | for (size_t i = 0; i < vm->sp; ++i) { | ||
| 398 | const char sep = (i == vm->sp - 1) ? '\n' : ' '; | ||
| 399 | printf("%x%c", vm->stack[i], sep); | ||
| 400 | } | ||
| 401 | printf("stack end\n"); | ||
| 402 | } | ||
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 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <stdbool.h> | ||
| 4 | #include <stddef.h> | ||
| 5 | #include <stdint.h> | ||
| 6 | |||
| 7 | typedef enum Op { | ||
| 8 | Exit, // Pop value from the stack and return as exit code. Return 0 if the | ||
| 9 | // stack is empty. | ||
| 10 | Push, | ||
| 11 | Pop, | ||
| 12 | Add, | ||
| 13 | Sub, | ||
| 14 | Mul, | ||
| 15 | Div, | ||
| 16 | Dec, // Decrement the top of the stack by 1. | ||
| 17 | Empty, // Check whether the stack is empty. Pushes a bool. | ||
| 18 | Cmp, // Pop the top of the stack and compare it with the payload. Pushes a | ||
| 19 | // bool. | ||
| 20 | /* Blocks */ | ||
| 21 | End, // Marks the end of a block. | ||
| 22 | Break, // Exit the current block. | ||
| 23 | Loop, // Push a loop block. Payload (i32): label. | ||
| 24 | /* Branches */ | ||
| 25 | Br, // Branch. Payload (i64): [(i32) conditional? | (i32) label]. | ||
| 26 | // A condtional branch pops a bool from the stack and branches if true. | ||
| 27 | // The condition can also be negated. See br_if(). | ||
| 28 | /* Functions */ | ||
| 29 | Func, | ||
| 30 | Arg, | ||
| 31 | Call, | ||
| 32 | /* Locals */ | ||
| 33 | Local, // Create a local variable. | ||
| 34 | LocalRd, // Load a local variable into the top of the stack. | ||
| 35 | LocalWr, // Pop the top of the stack and store it in a local variable. | ||
| 36 | } Op; | ||
| 37 | |||
| 38 | typedef enum Type { | ||
| 39 | I32, | ||
| 40 | F32, | ||
| 41 | } Type; | ||
| 42 | |||
| 43 | // Label type for blocks and locals. | ||
| 44 | typedef uint32_t Label; | ||
| 45 | |||
| 46 | typedef struct Branch { | ||
| 47 | Label label; | ||
| 48 | bool conditional : 1; // True for conditional branches. | ||
| 49 | bool expected : 1; // Comparison value for conditional branches. | ||
| 50 | } Branch; | ||
| 51 | |||
| 52 | typedef struct Function { | ||
| 53 | Label label; | ||
| 54 | } Function; | ||
| 55 | |||
| 56 | typedef struct Value { | ||
| 57 | union { | ||
| 58 | uint64_t u64; | ||
| 59 | int32_t i32; | ||
| 60 | float f32; | ||
| 61 | Branch branch; | ||
| 62 | Label label; | ||
| 63 | }; | ||
| 64 | } Value; | ||
| 65 | |||
| 66 | typedef struct Inst { | ||
| 67 | Op op : 5; | ||
| 68 | Type type : 2; | ||
| 69 | Value payload; | ||
| 70 | } Inst; | ||
| 71 | |||
| 72 | typedef struct Vm Vm; | ||
| 73 | |||
| 74 | // ----------------------------------------------------------------------------- | ||
| 75 | // VM API | ||
| 76 | |||
| 77 | /// Create a new virtual machine. | ||
| 78 | Vm* vm_new(); | ||
| 79 | |||
| 80 | /// Destroy the virtual machine. | ||
| 81 | void vm_del(Vm**); | ||
| 82 | |||
| 83 | /// Execute code on the virtual machine. | ||
| 84 | /// | ||
| 85 | /// Returns the program exit code if an exit operation is executed, 0 otherwise. | ||
| 86 | int vm_run(Vm*, const Inst[], size_t count); | ||
| 87 | |||
| 88 | /// Prints the virtual machine's stack to stdout. | ||
| 89 | void vm_print_stack(const Vm*); | ||
| 90 | |||
| 91 | // ----------------------------------------------------------------------------- | ||
| 92 | // Programming API | ||
| 93 | |||
| 94 | /// Exit the program. | ||
| 95 | static inline Inst vmExit() { return (Inst){.op = Exit}; } | ||
| 96 | |||
| 97 | /// Push a value. | ||
| 98 | static inline Inst vmPushI32(int32_t value) { | ||
| 99 | return (Inst){.op = Push, .type = I32, .payload = (Value){.i32 = value}}; | ||
| 100 | } | ||
| 101 | |||
| 102 | /// Pop a value. | ||
| 103 | static inline Inst vmPop(Type type) { return (Inst){.op = Pop, .type = type}; } | ||
| 104 | |||
| 105 | /// Add two values. | ||
| 106 | static inline Inst vmAdd(Type type) { return (Inst){.op = Add, .type = type}; } | ||
| 107 | |||
| 108 | /// Decrement a value. | ||
| 109 | static inline Inst vmDec(Type type) { return (Inst){.op = Dec, .type = type}; } | ||
| 110 | |||
| 111 | /// Compare a value. | ||
| 112 | static inline Inst vmCmpI32(int32_t value) { | ||
| 113 | return (Inst){.op = Cmp, .type = I32, .payload = (Value){.i32 = value}}; | ||
| 114 | } | ||
| 115 | |||
| 116 | /// End the current block. | ||
| 117 | static inline Inst vmEnd() { return (Inst){.op = End}; } | ||
| 118 | |||
| 119 | /// Create a loop. | ||
| 120 | static inline Inst vmLoop(Label label) { | ||
| 121 | return (Inst){.op = Loop, .payload = (Value){.label = label}}; | ||
| 122 | } | ||
| 123 | |||
| 124 | /// Create the payload of a conditional branch. | ||
| 125 | static inline Inst vmBr_if(bool value, Label label) { | ||
| 126 | return (Inst){ | ||
| 127 | .op = Br, | ||
| 128 | .payload = (Value){ | ||
| 129 | .branch = { | ||
| 130 | .label = label, | ||
| 131 | .conditional = 1, | ||
| 132 | .expected = value, | ||
| 133 | }}}; | ||
| 134 | } | ||
| 135 | |||
| 136 | /// Create a function. | ||
| 137 | static inline Inst vmFunc(Label label) { | ||
| 138 | return (Inst){.op = Func, .payload = (Value){.label = label}}; | ||
| 139 | } | ||
| 140 | |||
| 141 | /// Create a function argument. | ||
| 142 | static inline Inst vmArg(Type type, Label label) { | ||
| 143 | return (Inst){.op = Arg, .type = type, .payload = (Value){.label = label}}; | ||
| 144 | } | ||
| 145 | |||
| 146 | /// Call a function. | ||
| 147 | static inline Inst vmCall(Label label) { | ||
| 148 | return (Inst){.op = Call, .payload = (Value){.label = label}}; | ||
| 149 | } | ||
| 150 | |||
| 151 | /// Create a local variable. | ||
| 152 | static inline Inst vmLocal(Type type, Label label) { | ||
| 153 | return (Inst){.op = Local, .type = type, .payload = (Value){.label = label}}; | ||
| 154 | } | ||
| 155 | |||
| 156 | /// Read a local variable. | ||
| 157 | static inline Inst vmLocalRd(Type type, Label label) { | ||
| 158 | return (Inst){ | ||
| 159 | .op = LocalRd, .type = type, .payload = (Value){.label = label}}; | ||
| 160 | } | ||
| 161 | |||
| 162 | /// Write a local variable. | ||
| 163 | static inline Inst vmLocalWr(Type type, Label label) { | ||
| 164 | return (Inst){ | ||
| 165 | .op = LocalWr, .type = type, .payload = (Value){.label = label}}; | ||
| 166 | } | ||
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 @@ | |||
| 1 | // SPDX-License-Identifier: MIT | ||
| 2 | #pragma once | ||
| 3 | |||
| 4 | #ifdef UNIT_TEST | ||
| 5 | |||
| 6 | #include <stdbool.h> | ||
| 7 | #include <stdio.h> | ||
| 8 | #include <stdlib.h> | ||
| 9 | #include <string.h> | ||
| 10 | |||
| 11 | #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \ | ||
| 12 | defined(__NetBSD__) || defined(__OpenBSD__) | ||
| 13 | #define USE_SYSCTL_FOR_ARGS 1 | ||
| 14 | // clang-format off | ||
| 15 | #include <sys/types.h> | ||
| 16 | #include <sys/sysctl.h> | ||
| 17 | // clang-format on | ||
| 18 | #include <unistd.h> // getpid | ||
| 19 | #endif | ||
| 20 | |||
| 21 | struct test_file_metadata; | ||
| 22 | |||
| 23 | struct test_failure { | ||
| 24 | bool present; | ||
| 25 | const char *message; | ||
| 26 | const char *file; | ||
| 27 | int line; | ||
| 28 | }; | ||
| 29 | |||
| 30 | struct test_case_metadata { | ||
| 31 | void (*fn)(struct test_case_metadata *, struct test_file_metadata *); | ||
| 32 | struct test_failure failure; | ||
| 33 | const char *name; | ||
| 34 | struct test_case_metadata *next; | ||
| 35 | }; | ||
| 36 | |||
| 37 | struct test_file_metadata { | ||
| 38 | bool registered; | ||
| 39 | const char *name; | ||
| 40 | struct test_file_metadata *next; | ||
| 41 | struct test_case_metadata *tests; | ||
| 42 | }; | ||
| 43 | |||
| 44 | struct test_file_metadata __attribute__((weak)) * test_file_head; | ||
| 45 | |||
| 46 | #define SET_FAILURE(_message) \ | ||
| 47 | metadata->failure = (struct test_failure) { \ | ||
| 48 | .message = _message, .file = __FILE__, .line = __LINE__, .present = true, \ | ||
| 49 | } | ||
| 50 | |||
| 51 | #define TEST_EQUAL(a, b) \ | ||
| 52 | do { \ | ||
| 53 | if ((a) != (b)) { \ | ||
| 54 | SET_FAILURE(#a " != " #b); \ | ||
| 55 | return; \ | ||
| 56 | } \ | ||
| 57 | } while (0) | ||
| 58 | |||
| 59 | #define TEST_TRUE(a) \ | ||
| 60 | do { \ | ||
| 61 | if (!(a)) { \ | ||
| 62 | SET_FAILURE(#a " is not true"); \ | ||
| 63 | return; \ | ||
| 64 | } \ | ||
| 65 | } while (0) | ||
| 66 | |||
| 67 | #define TEST_STREQUAL(a, b) \ | ||
| 68 | do { \ | ||
| 69 | if (strcmp(a, b) != 0) { \ | ||
| 70 | SET_FAILURE(#a " != " #b); \ | ||
| 71 | return; \ | ||
| 72 | } \ | ||
| 73 | } while (0) | ||
| 74 | |||
| 75 | #define TEST_CASE(_name) \ | ||
| 76 | static void __test_h_##_name(struct test_case_metadata *, \ | ||
| 77 | struct test_file_metadata *); \ | ||
| 78 | static struct test_file_metadata __test_h_file; \ | ||
| 79 | static struct test_case_metadata __test_h_meta_##_name = { \ | ||
| 80 | .name = #_name, \ | ||
| 81 | .fn = __test_h_##_name, \ | ||
| 82 | }; \ | ||
| 83 | static void __attribute__((constructor(101))) __test_h_##_name##_register(void) { \ | ||
| 84 | __test_h_meta_##_name.next = __test_h_file.tests; \ | ||
| 85 | __test_h_file.tests = &__test_h_meta_##_name; \ | ||
| 86 | if (!__test_h_file.registered) { \ | ||
| 87 | __test_h_file.name = __FILE__; \ | ||
| 88 | __test_h_file.next = test_file_head; \ | ||
| 89 | test_file_head = &__test_h_file; \ | ||
| 90 | __test_h_file.registered = true; \ | ||
| 91 | } \ | ||
| 92 | } \ | ||
| 93 | static void __test_h_##_name( \ | ||
| 94 | struct test_case_metadata *metadata __attribute__((unused)), \ | ||
| 95 | struct test_file_metadata *file_metadata __attribute__((unused))) | ||
| 96 | |||
| 97 | extern void __attribute__((weak)) (*test_h_unittest_setup)(void); | ||
| 98 | /// Run defined tests, return true if all tests succeeds | ||
| 99 | /// @param[out] tests_run if not NULL, set to whether tests were run | ||
| 100 | static inline void __attribute__((constructor(102))) run_tests(void) { | ||
| 101 | bool should_run = false; | ||
| 102 | #ifdef USE_SYSCTL_FOR_ARGS | ||
| 103 | int mib[] = { | ||
| 104 | CTL_KERN, | ||
| 105 | #if defined(__NetBSD__) || defined(__OpenBSD__) | ||
| 106 | KERN_PROC_ARGS, | ||
| 107 | getpid(), | ||
| 108 | KERN_PROC_ARGV, | ||
| 109 | #else | ||
| 110 | KERN_PROC, | ||
| 111 | KERN_PROC_ARGS, | ||
| 112 | getpid(), | ||
| 113 | #endif | ||
| 114 | }; | ||
| 115 | char *arg = NULL; | ||
| 116 | size_t arglen; | ||
| 117 | sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &arglen, NULL, 0); | ||
| 118 | arg = malloc(arglen); | ||
| 119 | sysctl(mib, sizeof(mib) / sizeof(mib[0]), arg, &arglen, NULL, 0); | ||
| 120 | #else | ||
| 121 | FILE *cmdlinef = fopen("/proc/self/cmdline", "r"); | ||
| 122 | char *arg = NULL; | ||
| 123 | int arglen; | ||
| 124 | fscanf(cmdlinef, "%ms%n", &arg, &arglen); | ||
| 125 | fclose(cmdlinef); | ||
| 126 | #endif | ||
| 127 | for (char *pos = arg; pos < arg + arglen; pos += strlen(pos) + 1) { | ||
| 128 | if (strcmp(pos, "--unittest") == 0) { | ||
| 129 | should_run = true; | ||
| 130 | break; | ||
| 131 | } | ||
| 132 | } | ||
| 133 | free(arg); | ||
| 134 | |||
| 135 | if (!should_run) { | ||
| 136 | return; | ||
| 137 | } | ||
| 138 | |||
| 139 | if (&test_h_unittest_setup) { | ||
| 140 | test_h_unittest_setup(); | ||
| 141 | } | ||
| 142 | |||
| 143 | struct test_file_metadata *i = test_file_head; | ||
| 144 | int failed = 0, success = 0; | ||
| 145 | while (i) { | ||
| 146 | fprintf(stderr, "Running tests from %s:\n", i->name); | ||
| 147 | struct test_case_metadata *j = i->tests; | ||
| 148 | while (j) { | ||
| 149 | fprintf(stderr, "\t%s ... ", j->name); | ||
| 150 | j->failure.present = false; | ||
| 151 | j->fn(j, i); | ||
| 152 | if (j->failure.present) { | ||
| 153 | fprintf(stderr, "failed (%s at %s:%d)\n", j->failure.message, | ||
| 154 | j->failure.file, j->failure.line); | ||
| 155 | failed++; | ||
| 156 | } else { | ||
| 157 | fprintf(stderr, "passed\n"); | ||
| 158 | success++; | ||
| 159 | } | ||
| 160 | j = j->next; | ||
| 161 | } | ||
| 162 | fprintf(stderr, "\n"); | ||
| 163 | i = i->next; | ||
| 164 | } | ||
| 165 | int total = failed + success; | ||
| 166 | fprintf(stderr, "Test results: passed %d/%d, failed %d/%d\n", success, total, | ||
| 167 | failed, total); | ||
| 168 | exit(failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE); | ||
| 169 | } | ||
| 170 | |||
| 171 | #else | ||
| 172 | |||
| 173 | #include <stdbool.h> | ||
| 174 | |||
| 175 | #define TEST_CASE(name) static void __attribute__((unused)) __test_h_##name(void) | ||
| 176 | |||
| 177 | #define TEST_EQUAL(a, b) \ | ||
| 178 | (void)(a); \ | ||
| 179 | (void)(b) | ||
| 180 | #define TEST_TRUE(a) (void)(a) | ||
| 181 | #define TEST_STREQUAL(a, b) \ | ||
| 182 | (void)(a); \ | ||
| 183 | (void)(b) | ||
| 184 | |||
| 185 | #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 @@ | |||
| 1 | #include "vm.h" | ||
| 2 | |||
| 3 | #include "test.h" | ||
| 4 | |||
| 5 | #include <stdio.h> | ||
| 6 | |||
| 7 | /// Create and destroy a vm. | ||
| 8 | TEST_CASE(vm_create_destroy) { | ||
| 9 | Vm* vm = vm_new(); | ||
| 10 | TEST_TRUE(vm != 0); | ||
| 11 | vm_del(&vm); | ||
| 12 | } | ||
| 13 | |||
| 14 | // Exit with an implicit 0 exit code. | ||
| 15 | TEST_CASE(vm_exit_implicit) { | ||
| 16 | // clang-format off | ||
| 17 | const Inst instructions[] = { | ||
| 18 | vmExit(), | ||
| 19 | }; | ||
| 20 | // clang-format on | ||
| 21 | |||
| 22 | Vm* vm = vm_new(); | ||
| 23 | TEST_TRUE(vm != 0); | ||
| 24 | const int exit_code = | ||
| 25 | vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst)); | ||
| 26 | TEST_TRUE(exit_code == 0); | ||
| 27 | vm_del(&vm); | ||
| 28 | } | ||
| 29 | |||
| 30 | // Exit with an explicit exit code. | ||
| 31 | TEST_CASE(vm_exit_explicit) { | ||
| 32 | const int32_t expected = 17; | ||
| 33 | |||
| 34 | // clang-format off | ||
| 35 | const Inst instructions[] = { | ||
| 36 | vmPushI32(expected), | ||
| 37 | vmExit(), | ||
| 38 | }; | ||
| 39 | // clang-format on | ||
| 40 | |||
| 41 | Vm* vm = vm_new(); | ||
| 42 | TEST_TRUE(vm != 0); | ||
| 43 | const int exit_code = | ||
| 44 | vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst)); | ||
| 45 | TEST_TRUE(exit_code == expected); | ||
| 46 | vm_del(&vm); | ||
| 47 | } | ||
| 48 | |||
| 49 | /// Add two i32 numbers. | ||
| 50 | TEST_CASE(vm_add_i32) { | ||
| 51 | const int n1 = 2; | ||
| 52 | const int n2 = 3; | ||
| 53 | |||
| 54 | // clang-format off | ||
| 55 | const Inst instructions[] = { | ||
| 56 | vmPushI32(n1), | ||
| 57 | vmPushI32(n2), | ||
| 58 | vmAdd(I32), | ||
| 59 | vmExit(), | ||
| 60 | }; | ||
| 61 | // clang-format on | ||
| 62 | |||
| 63 | Vm* vm = vm_new(); | ||
| 64 | TEST_TRUE(vm != 0); | ||
| 65 | const int exit_code = | ||
| 66 | vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst)); | ||
| 67 | TEST_EQUAL(exit_code, n1 + n2); | ||
| 68 | vm_del(&vm); | ||
| 69 | } | ||
| 70 | |||
| 71 | /// Sum an array of numbers with 4 add instructions. | ||
| 72 | TEST_CASE(vm_sum_array_i32_explicit) { | ||
| 73 | const int vals[5] = {1, 2, 3, 4, 5}; | ||
| 74 | |||
| 75 | // clang-format off | ||
| 76 | const Inst instructions[] = { | ||
| 77 | vmPushI32(vals[0]), | ||
| 78 | vmPushI32(vals[1]), | ||
| 79 | vmPushI32(vals[2]), | ||
| 80 | vmPushI32(vals[3]), | ||
| 81 | vmPushI32(vals[4]), | ||
| 82 | vmAdd(I32), | ||
| 83 | vmAdd(I32), | ||
| 84 | vmAdd(I32), | ||
| 85 | vmAdd(I32), | ||
| 86 | vmExit(), | ||
| 87 | }; | ||
| 88 | // clang-format on | ||
| 89 | |||
| 90 | int sum = 0; | ||
| 91 | for (size_t i = 0; i < sizeof(vals) / sizeof(vals[0]); ++i) { | ||
| 92 | sum += vals[i]; | ||
| 93 | } | ||
| 94 | |||
| 95 | Vm* vm = vm_new(); | ||
| 96 | TEST_TRUE(vm != 0); | ||
| 97 | const int exit_code = | ||
| 98 | vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst)); | ||
| 99 | TEST_EQUAL(exit_code, sum); | ||
| 100 | vm_del(&vm); | ||
| 101 | } | ||
| 102 | |||
| 103 | /// Sum an array of numbers with a loop. | ||
| 104 | TEST_CASE(vm_sum_array_i32_loop) { | ||
| 105 | const int vals[5] = {1, 2, 3, 4, 5}; | ||
| 106 | |||
| 107 | const Label loop_label = 0; | ||
| 108 | const Label counter_index = 0; | ||
| 109 | |||
| 110 | // clang-format off | ||
| 111 | const Inst instructions[] = { | ||
| 112 | vmPushI32(vals[0]), | ||
| 113 | vmPushI32(vals[1]), | ||
| 114 | vmPushI32(vals[2]), | ||
| 115 | vmPushI32(vals[3]), | ||
| 116 | vmPushI32(vals[4]), | ||
| 117 | vmLocal(I32, counter_index), | ||
| 118 | vmPushI32(sizeof(vals) / sizeof(vals[0]) - 1), | ||
| 119 | vmLocalWr(I32, counter_index), | ||
| 120 | vmLoop(loop_label), | ||
| 121 | vmAdd(I32), | ||
| 122 | vmLocalRd(I32, counter_index), | ||
| 123 | vmDec(I32), | ||
| 124 | // TODO: Could be useful to have a function that writes the local but | ||
| 125 | // leaves its value on the stack. | ||
| 126 | vmLocalWr(I32, counter_index), | ||
| 127 | vmLocalRd(I32, counter_index), | ||
| 128 | // TODO: Perhaps we should expect the comparison value to also be pushed | ||
| 129 | // to the stack. | ||
| 130 | vmCmpI32(0), | ||
| 131 | vmBr_if(false, loop_label), | ||
| 132 | vmEnd(), | ||
| 133 | vmExit(), | ||
| 134 | }; | ||
| 135 | // clang-format on | ||
| 136 | |||
| 137 | int sum = 0; | ||
| 138 | for (size_t i = 0; i < sizeof(vals) / sizeof(vals[0]); ++i) { | ||
| 139 | sum += vals[i]; | ||
| 140 | } | ||
| 141 | |||
| 142 | Vm* vm = vm_new(); | ||
| 143 | TEST_TRUE(vm != 0); | ||
| 144 | const int exit_code = | ||
| 145 | vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst)); | ||
| 146 | TEST_EQUAL(exit_code, sum); | ||
| 147 | vm_del(&vm); | ||
| 148 | } | ||
| 149 | |||
| 150 | // Call a function to add two numbers. | ||
| 151 | TEST_CASE(vm_function_call) { | ||
| 152 | const Label func = 0; | ||
| 153 | const Label a = 0; | ||
| 154 | const Label b = 1; | ||
| 155 | const int32_t a_val = 3; | ||
| 156 | const int32_t b_val = 5; | ||
| 157 | const int32_t expected = a + b; | ||
| 158 | |||
| 159 | // clang-format off | ||
| 160 | const Inst instructions[] = { | ||
| 161 | /* Function definition */ | ||
| 162 | vmFunc(func), | ||
| 163 | vmArg(I32, b), | ||
| 164 | vmArg(I32, a), | ||
| 165 | vmAdd(I32), | ||
| 166 | vmEnd(), | ||
| 167 | /* Main program */ | ||
| 168 | vmPushI32(a_val), | ||
| 169 | vmPushI32(b_val), | ||
| 170 | vmCall(func), | ||
| 171 | vmExit(), | ||
| 172 | }; | ||
| 173 | // clang-format on | ||
| 174 | |||
| 175 | Vm* vm = vm_new(); | ||
| 176 | TEST_TRUE(vm != 0); | ||
| 177 | // const int exit_code = | ||
| 178 | // vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst)); | ||
| 179 | vm_del(&vm); | ||
| 180 | } | ||
| 181 | |||
| 182 | 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 @@ | |||
| 1 | cmake_minimum_required(VERSION 3.0) | ||
| 2 | |||
| 3 | project(vmrun) | ||
| 4 | |||
| 5 | add_executable(vmrun | ||
| 6 | src/main.c) | ||
| 7 | |||
| 8 | target_link_libraries(vmrun | ||
| 9 | vm) | ||
| 10 | |||
| 11 | 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 @@ | |||
| 1 | #include <vm.h> | ||
| 2 | |||
| 3 | #include <stdbool.h> | ||
| 4 | #include <stdio.h> | ||
| 5 | #include <string.h> | ||
| 6 | |||
| 7 | typedef enum CommandType { | ||
| 8 | ExitSession, | ||
| 9 | PrintStack, | ||
| 10 | } CommandType; | ||
| 11 | |||
| 12 | typedef struct Command { | ||
| 13 | CommandType type; | ||
| 14 | } Command; | ||
| 15 | |||
| 16 | static bool read_command(char line[], Command* pCmd) { | ||
| 17 | static const char* delim = " \n"; | ||
| 18 | |||
| 19 | const char* cmd = strtok(line, delim); | ||
| 20 | if (strcmp(cmd, "exit") == 0) { | ||
| 21 | *pCmd = (Command){.type = ExitSession}; | ||
| 22 | return true; | ||
| 23 | } else if (strcmp(cmd, "print_stack") == 0) { | ||
| 24 | *pCmd = (Command){.type = PrintStack}; | ||
| 25 | return true; | ||
| 26 | } | ||
| 27 | return false; | ||
| 28 | } | ||
| 29 | |||
| 30 | /// Runs a command. | ||
| 31 | /// | ||
| 32 | /// Returns true unless on ExitSession. | ||
| 33 | static bool run_command(Vm* vm, Command cmd) { | ||
| 34 | switch (cmd.type) { | ||
| 35 | case ExitSession: | ||
| 36 | return false; | ||
| 37 | case PrintStack: | ||
| 38 | vm_print_stack(vm); | ||
| 39 | break; | ||
| 40 | } | ||
| 41 | return true; | ||
| 42 | } | ||
| 43 | |||
| 44 | int main() { | ||
| 45 | Vm* vm = vm_new(); | ||
| 46 | if (!vm) { | ||
| 47 | fprintf(stderr, "Failed to create VM\n"); | ||
| 48 | return 1; | ||
| 49 | } | ||
| 50 | |||
| 51 | Command cmd; | ||
| 52 | bool should_continue = true; | ||
| 53 | char line[128]; | ||
| 54 | |||
| 55 | while (should_continue && fgets(line, sizeof(line), stdin)) { | ||
| 56 | if (read_command(line, &cmd)) { | ||
| 57 | should_continue = run_command(vm, cmd); | ||
| 58 | } else { | ||
| 59 | fprintf(stderr, "Unknown command\n"); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | vm_del(&vm); | ||
| 64 | return 0; | ||
| 65 | } | ||
