tuxanci2-engine
Game engine for tuxanci2
Log | Files | Refs | README | LICENSE
commit 4990e297f0e3d8a2cd709f88148d08e1c56a9e3b
parent 9d4f5eb4f036b09a58c67535fe3209d25454df5e
Author: Connor Thomson <connor@gnufault.org>
Date:   Sat, 24 Oct 2026 19:26:06 -0700

Add the tavm

Diffstat:
M.gitignore | 2--
Minclude/ta_api.h | 8+++++++-
Ainclude/ta_fs.h | 11+++++++++++
Ainclude/ta_mod.h | 30++++++++++++++++++++++++++++++
Ainclude/ta_vm.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Mplatforms/aalib/aalib.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mplatforms/dos/dos.c | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mplatforms/dreamcast/dreamcast.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mplatforms/n64/n64.c | 20++++++++++++++++++++
Aplatforms/ps1/nugget | 1+
Mplatforms/saturn/saturn.c | 20++++++++++++++++++++
Mplatforms/sdl2/sdl2.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mplatforms/sdl3/sdl3.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mplatforms/wayland/wayland.c | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mplatforms/win32/win32.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mplatforms/x11/x11.c | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mplatforms/xbox/xbox.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ta_main.c | 5+++++
Asrc/ta_mod.c | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ta_vm.c | 441+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
20 files changed, 1270 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,2 +1 @@ build -tuxanci2* -\ No newline at end of file diff --git a/include/ta_api.h b/include/ta_api.h @@ -10,6 +10,7 @@ extern "C" { #include "ta_keyboard.h" #include "ta_title.h" #include "ta_types.h" +#include "ta_fs.h" // Must be called before ta_update() void ta_main_init(void); @@ -17,10 +18,15 @@ void ta_main_init(void); // Must be called every frame after ta_init() ta_bool ta_main_update(void); -// Misc stuff that you need to implement/wrap +// Memory allocation void *ta_malloc(ta_size size); void *ta_realloc(void *ptr, ta_size new_size); void ta_free(void *ptr); + +// File system +ta_fs_file ta_read_file(const char *path); +void ta_close_file(ta_fs_file *file); + void ta_exit(int status); #ifdef __cplusplus diff --git a/include/ta_fs.h b/include/ta_fs.h @@ -0,0 +1,11 @@ +#ifndef TA_FS_H +#define TA_FS_H + +#include "ta_types.h" + +typedef struct { + char *data; + ta_size size; +} ta_fs_file; + +#endif // TA_FS_H diff --git a/include/ta_mod.h b/include/ta_mod.h @@ -0,0 +1,30 @@ +#ifndef TA_MOD_H +#define TA_MOD_H + +#include "ta_types.h" +#include "ta_vm.h" + +#ifndef TA_MOD_RAM_SIZE +#define TA_MOD_RAM_SIZE (256u * 1024u) +#endif + +#ifndef TA_MOD_STEPS_PER_FRAME +#define TA_MOD_STEPS_PER_FRAME 200000u +#endif + +#define TA_SYS_EXIT 0 +#define TA_SYS_YIELD 1 +#define TA_SYS_CLEAR 2 + +extern ta_bool ta_mod_running; + +ta_bool ta_mod_load(const ta_uint8 *binary, ta_size size); +ta_bool ta_mod_load_file(const char *path); +ta_bool ta_mod_update(void); +ta_vm_status ta_mod_status(void); +ta_int32 ta_mod_exit_code(void); +const ta_vm *ta_mod_vm(void); + +void ta_mod_unload(void); + +#endif // TA_MOD_H diff --git a/include/ta_vm.h b/include/ta_vm.h @@ -0,0 +1,46 @@ +#ifndef TA_VM_H +#define TA_VM_H + +#include "ta_types.h" + +typedef enum { + TA_VM_RUNNING, + TA_VM_HALTED, + TA_VM_FAULT_ILLEGAL, + TA_VM_FAULT_MEMORY, + TA_VM_FAULT_MISALIGNED, + TA_VM_FAULT_EBREAK +} ta_vm_status; + +typedef struct ta_vm ta_vm; +typedef ta_vm_status (*ta_vm_ecall)(ta_vm *vm, void *user); + +struct ta_vm { + ta_uint32 regs[32]; + ta_uint32 pc; + ta_uint8 *mem; + ta_uint32 mem_size; + ta_vm_ecall ecall; + void *user; + ta_uint32 steps; +}; + +#define TA_VM_REG_RA 1 +#define TA_VM_REG_SP 2 +#define TA_VM_REG_A0 10 +#define TA_VM_REG_A1 11 +#define TA_VM_REG_A2 12 +#define TA_VM_REG_A3 13 +#define TA_VM_REG_A4 14 +#define TA_VM_REG_A5 15 +#define TA_VM_REG_A6 16 +#define TA_VM_REG_A7 17 + +void ta_vm_init(ta_vm *vm, ta_uint8 *mem, ta_uint32 mem_size, ta_vm_ecall ecall, void *user); +void ta_vm_reset(ta_vm *vm, ta_uint32 entry, ta_uint32 stack_top); +ta_vm_status ta_vm_run(ta_vm *vm, ta_uint32 max_steps); +ta_bool ta_vm_read8(const ta_vm *vm, ta_uint32 addr, ta_uint8 *out); +ta_bool ta_vm_read32(const ta_vm *vm, ta_uint32 addr, ta_uint32 *out); +ta_bool ta_vm_write32(ta_vm *vm, ta_uint32 addr, ta_uint32 value); + +#endif // TA_VM_H diff --git a/platforms/aalib/aalib.c b/platforms/aalib/aalib.c @@ -4,6 +4,7 @@ #include <stdlib.h> #include <stdint.h> #include <stdbool.h> +#include <stdio.h> #define WINDOW_WIDTH 800 #define WINDOW_HEIGHT 600 @@ -182,6 +183,69 @@ void ta_free(void *ptr) { free(ptr); } +ta_fs_file ta_read_file(const char *path) { + ta_fs_file file = { + .data = NULL, + .size = 0 + }; + + FILE *file_pointer = fopen(path, "rb"); + if (!file_pointer) { + return file; + } + + if (fseek(file_pointer, 0, SEEK_END) != 0) { + fclose(file_pointer); + + return file; + } + + long file_size = ftell(file_pointer); + if (file_size < 0) { + fclose(file_pointer); + + return file; + } + + rewind(file_pointer); + + file.data = ta_malloc((ta_size)file_size); + if (!file.data) { + fclose(file_pointer); + + return file; + } + + ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer); + + if (bytes_read != (ta_size)file_size) { + ta_free(file.data); + file.data = NULL; + fclose(file_pointer); + + return file; + } + + file.size = (ta_size)file_size; + + fclose(file_pointer); + + return file; +} + +void ta_close_file(ta_fs_file *file) { + if (!file) { + return; + } + + if (file->data) { + ta_free(file->data); + file->data = NULL; + } + + file->size = 0; +} + void ta_exit(int status) { exit(status); } diff --git a/platforms/dos/dos.c b/platforms/dos/dos.c @@ -202,6 +202,69 @@ void ta_free(void *ptr) { free(ptr); } +ta_fs_file ta_read_file(const char *path) { + ta_fs_file file = { + .data = NULL, + .size = 0 + }; + + FILE *file_pointer = fopen(path, "rb"); + if (!file_pointer) { + return file; + } + + if (fseek(file_pointer, 0, SEEK_END) != 0) { + fclose(file_pointer); + + return file; + } + + long file_size = ftell(file_pointer); + if (file_size < 0) { + fclose(file_pointer); + + return file; + } + + rewind(file_pointer); + + file.data = ta_malloc((ta_size)file_size); + if (!file.data) { + fclose(file_pointer); + + return file; + } + + ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer); + + if (bytes_read != (ta_size)file_size) { + ta_free(file.data); + file.data = NULL; + fclose(file_pointer); + + return file; + } + + file.size = (ta_size)file_size; + + fclose(file_pointer); + + return file; +} + +void ta_close_file(ta_fs_file *file) { + if (!file) { + return; + } + + if (file->data) { + ta_free(file->data); + file->data = NULL; + } + + file->size = 0; +} + void ta_exit(int status) { exit(status); } diff --git a/platforms/dreamcast/dreamcast.c b/platforms/dreamcast/dreamcast.c @@ -5,6 +5,7 @@ #include <dc/pvr.h> #include <stdint.h> #include <stdlib.h> +#include <stdio.h> KOS_INIT_FLAGS(INIT_DEFAULT); @@ -171,6 +172,69 @@ void ta_free(void *ptr) { free(ptr); } +ta_fs_file ta_read_file(const char *path) { + ta_fs_file file = { + .data = NULL, + .size = 0 + }; + + FILE *file_pointer = fopen(path, "rb"); + if (!file_pointer) { + return file; + } + + if (fseek(file_pointer, 0, SEEK_END) != 0) { + fclose(file_pointer); + + return file; + } + + long file_size = ftell(file_pointer); + if (file_size < 0) { + fclose(file_pointer); + + return file; + } + + rewind(file_pointer); + + file.data = ta_malloc((ta_size)file_size); + if (!file.data) { + fclose(file_pointer); + + return file; + } + + ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer); + + if (bytes_read != (ta_size)file_size) { + ta_free(file.data); + file.data = NULL; + fclose(file_pointer); + + return file; + } + + file.size = (ta_size)file_size; + + fclose(file_pointer); + + return file; +} + +void ta_close_file(ta_fs_file *file) { + if (!file) { + return; + } + + if (file->data) { + ta_free(file->data); + file->data = NULL; + } + + file->size = 0; +} + void ta_exit(int status) { (void)status; diff --git a/platforms/n64/n64.c b/platforms/n64/n64.c @@ -101,6 +101,26 @@ void ta_free(void *ptr) { free(ptr); } +ta_fs_file ta_read_file(const char *path) { + ta_fs_file file = { + .data = NULL, + .size = 0 + }; + + (void)path; + + return file; +} + +void ta_close_file(ta_fs_file *file) { + if (!file) { + return; + } + + file->data = NULL; + file->size = 0; +} + void ta_exit(int status) { exit(status); } diff --git a/platforms/ps1/nugget b/platforms/ps1/nugget @@ -0,0 +1 @@ +Subproject commit 58d0cd1c5aa80f3eb338c8b94f70946314c9af36 diff --git a/platforms/saturn/saturn.c b/platforms/saturn/saturn.c @@ -209,6 +209,26 @@ void ta_free(void *ptr) { } } +ta_fs_file ta_read_file(const char *path) { + ta_fs_file file = { + .data = NULL, + .size = 0 + }; + + (void)path; + + return file; +} + +void ta_close_file(ta_fs_file *file) { + if (!file) { + return; + } + + file->data = NULL; + file->size = 0; +} + void ta_exit(int status) { (void)status; diff --git a/platforms/sdl2/sdl2.c b/platforms/sdl2/sdl2.c @@ -3,6 +3,7 @@ #include <SDL2/SDL.h> #include <unistd.h> #include <stdlib.h> +#include <stdio.h> #define WINDOW_WIDTH 800 #define WINDOW_HEIGHT 600 @@ -132,6 +133,69 @@ void ta_free(void *ptr) { free(ptr); } +ta_fs_file ta_read_file(const char *path) { + ta_fs_file file = { + .data = NULL, + .size = 0 + }; + + FILE *file_pointer = fopen(path, "rb"); + if (!file_pointer) { + return file; + } + + if (fseek(file_pointer, 0, SEEK_END) != 0) { + fclose(file_pointer); + + return file; + } + + long file_size = ftell(file_pointer); + if (file_size < 0) { + fclose(file_pointer); + + return file; + } + + rewind(file_pointer); + + file.data = ta_malloc((ta_size)file_size); + if (!file.data) { + fclose(file_pointer); + + return file; + } + + ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer); + + if (bytes_read != (ta_size)file_size) { + ta_free(file.data); + file.data = NULL; + fclose(file_pointer); + + return file; + } + + file.size = (ta_size)file_size; + + fclose(file_pointer); + + return file; +} + +void ta_close_file(ta_fs_file *file) { + if (!file) { + return; + } + + if (file->data) { + ta_free(file->data); + file->data = NULL; + } + + file->size = 0; +} + void ta_exit(int status) { exit(status); } diff --git a/platforms/sdl3/sdl3.c b/platforms/sdl3/sdl3.c @@ -3,6 +3,7 @@ #include <SDL3/SDL.h> #include <unistd.h> #include <stdlib.h> +#include <stdio.h> #define WINDOW_WIDTH 800 #define WINDOW_HEIGHT 600 @@ -140,6 +141,69 @@ void ta_free(void *ptr) { free(ptr); } +ta_fs_file ta_read_file(const char *path) { + ta_fs_file file = { + .data = NULL, + .size = 0 + }; + + FILE *file_pointer = fopen(path, "rb"); + if (!file_pointer) { + return file; + } + + if (fseek(file_pointer, 0, SEEK_END) != 0) { + fclose(file_pointer); + + return file; + } + + long file_size = ftell(file_pointer); + if (file_size < 0) { + fclose(file_pointer); + + return file; + } + + rewind(file_pointer); + + file.data = ta_malloc((ta_size)file_size); + if (!file.data) { + fclose(file_pointer); + + return file; + } + + ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer); + + if (bytes_read != (ta_size)file_size) { + ta_free(file.data); + file.data = NULL; + fclose(file_pointer); + + return file; + } + + file.size = (ta_size)file_size; + + fclose(file_pointer); + + return file; +} + +void ta_close_file(ta_fs_file *file) { + if (!file) { + return; + } + + if (file->data) { + ta_free(file->data); + file->data = NULL; + } + + file->size = 0; +} + void ta_exit(int status) { exit(status); } diff --git a/platforms/wayland/wayland.c b/platforms/wayland/wayland.c @@ -561,6 +561,69 @@ void ta_free(void *ptr) { free(ptr); } +ta_fs_file ta_read_file(const char *path) { + ta_fs_file file = { + .data = NULL, + .size = 0 + }; + + FILE *file_pointer = fopen(path, "rb"); + if (!file_pointer) { + return file; + } + + if (fseek(file_pointer, 0, SEEK_END) != 0) { + fclose(file_pointer); + + return file; + } + + long file_size = ftell(file_pointer); + if (file_size < 0) { + fclose(file_pointer); + + return file; + } + + rewind(file_pointer); + + file.data = ta_malloc((ta_size)file_size); + if (!file.data) { + fclose(file_pointer); + + return file; + } + + ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer); + + if (bytes_read != (ta_size)file_size) { + ta_free(file.data); + file.data = NULL; + fclose(file_pointer); + + return file; + } + + file.size = (ta_size)file_size; + + fclose(file_pointer); + + return file; +} + +void ta_close_file(ta_fs_file *file) { + if (!file) { + return; + } + + if (file->data) { + ta_free(file->data); + file->data = NULL; + } + + file->size = 0; +} + void ta_exit(int status) { exit(status); } diff --git a/platforms/win32/win32.c b/platforms/win32/win32.c @@ -2,6 +2,7 @@ #include <windows.h> #include <stdlib.h> #include <stdbool.h> +#include <stdio.h> #define WINDOW_WIDTH 800 #define WINDOW_HEIGHT 600 @@ -201,6 +202,69 @@ void ta_free(void *ptr) { free(ptr); } +ta_fs_file ta_read_file(const char *path) { + ta_fs_file file = { + .data = NULL, + .size = 0 + }; + + FILE *file_pointer = fopen(path, "rb"); + if (!file_pointer) { + return file; + } + + if (fseek(file_pointer, 0, SEEK_END) != 0) { + fclose(file_pointer); + + return file; + } + + long file_size = ftell(file_pointer); + if (file_size < 0) { + fclose(file_pointer); + + return file; + } + + rewind(file_pointer); + + file.data = ta_malloc((ta_size)file_size); + if (!file.data) { + fclose(file_pointer); + + return file; + } + + ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer); + + if (bytes_read != (ta_size)file_size) { + ta_free(file.data); + file.data = NULL; + fclose(file_pointer); + + return file; + } + + file.size = (ta_size)file_size; + + fclose(file_pointer); + + return file; +} + +void ta_close_file(ta_fs_file *file) { + if (!file) { + return; + } + + if (file->data) { + ta_free(file->data); + file->data = NULL; + } + + file->size = 0; +} + void ta_exit(int status) { exit(status); } diff --git a/platforms/x11/x11.c b/platforms/x11/x11.c @@ -1,6 +1,7 @@ #include "ta_api.h" #include <unistd.h> #include <stdlib.h> +#include <stdio.h> #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/keysym.h> @@ -198,6 +199,70 @@ void ta_free(void *ptr) { free(ptr); } +ta_fs_file ta_read_file(const char *path) { + ta_fs_file file = { + .data = NULL, + .size = 0 + }; + + FILE *file_pointer = fopen(path, "rb"); + if (!file_pointer) { + return file; + } + + if (fseek(file_pointer, 0, SEEK_END) != 0) { + fclose(file_pointer); + + return file; + } + + long file_size = ftell(file_pointer); + if (file_size == -1) { + fclose(file_pointer); + + return file; + } + + rewind(file_pointer); + + file.data = ta_malloc(file_size); + if (!file.data) { + fclose(file_pointer); + + return file; + } + + ta_size element_size = 1; + ta_size bytes_read = fread(file.data, element_size, file_size, file_pointer); + + if (bytes_read != (ta_size)file_size) { + ta_free(file.data); + file.data = NULL; + fclose(file_pointer); + + return file; + } + + file.size = file_size; + + fclose(file_pointer); + + return file; +} + +void ta_close_file(ta_fs_file *file) { + if (!file) { + return; + } + + if (file->data) { + ta_free(file->data); + file->data = NULL; + } + + file->size = 0; +} + void ta_exit(int status) { exit(status); } diff --git a/platforms/xbox/xbox.c b/platforms/xbox/xbox.c @@ -4,6 +4,7 @@ #include <windows.h> #include <stdint.h> #include <stdlib.h> +#include <stdio.h> #define SCREEN_WIDTH 640 #define SCREEN_HEIGHT 480 @@ -113,6 +114,69 @@ void ta_free(void *ptr) { free(ptr); } +ta_fs_file ta_read_file(const char *path) { + ta_fs_file file = { + .data = NULL, + .size = 0 + }; + + FILE *file_pointer = fopen(path, "rb"); + if (!file_pointer) { + return file; + } + + if (fseek(file_pointer, 0, SEEK_END) != 0) { + fclose(file_pointer); + + return file; + } + + long file_size = ftell(file_pointer); + if (file_size < 0) { + fclose(file_pointer); + + return file; + } + + rewind(file_pointer); + + file.data = ta_malloc((ta_size)file_size); + if (!file.data) { + fclose(file_pointer); + + return file; + } + + ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer); + + if (bytes_read != (ta_size)file_size) { + ta_free(file.data); + file.data = NULL; + fclose(file_pointer); + + return file; + } + + file.size = (ta_size)file_size; + + fclose(file_pointer); + + return file; +} + +void ta_close_file(ta_fs_file *file) { + if (!file) { + return; + } + + if (file->data) { + ta_free(file->data); + file->data = NULL; + } + + file->size = 0; +} + void ta_exit(int status) { exit(status); } diff --git a/src/ta_main.c b/src/ta_main.c @@ -1,6 +1,7 @@ #include "ta_api.h" #include "ta_files.h" #include "ta_screen.h" +#include "ta_mod.h" void ta_main_init(void) { if (!ta_display_init() || (!ta_display->sprite_mode && !ta_fb_init(ta_display))) { @@ -10,6 +11,10 @@ void ta_main_init(void) { } ta_bool ta_main_update(void) { + if (ta_mod_running) { + ta_mod_update(); + } + if (!ta_screen_update()) { return ta_false; } diff --git a/src/ta_mod.c b/src/ta_mod.c @@ -0,0 +1,114 @@ +#include "ta_mod.h" +#include "ta_api.h" +#include "ta_display.h" +#include "ta_fb.h" + +static ta_vm vm; +static ta_uint8 *ram; +static ta_bool loaded; +static ta_vm_status status; +static ta_int32 exit_code; +ta_bool ta_mod_running; + +static ta_vm_status mod_ecall(ta_vm *machine, void *user) { + (void)user; + + ta_uint32 *r = machine->regs; + ta_uint32 *args = &r[TA_VM_REG_A0]; + + switch (r[TA_VM_REG_A7]) { + case TA_SYS_EXIT: { + exit_code = (ta_int32)args[0]; + + return TA_VM_HALTED; + } + case TA_SYS_CLEAR: { + if (ta_display->sprite_mode) { + ta_display_sprites_clear(); + } else { + ta_fb_clear(ta_display); + } + + r[TA_VM_REG_A0] = 0; + + return TA_VM_RUNNING; + } + default: { + return TA_VM_FAULT_ILLEGAL; + } + } +} + +ta_bool ta_mod_load(const ta_uint8 *binary, ta_size size) { + ta_mod_unload(); + + if (!binary || size == 0 || size > TA_MOD_RAM_SIZE) { + return ta_false; + } + + ram = (ta_uint8 *)ta_malloc(TA_MOD_RAM_SIZE); + + if (!ram) { + return ta_false; + } + + for (ta_size i = 0; i < TA_MOD_RAM_SIZE; i++) { + ram[i] = i < size ? binary[i] : 0; + } + + ta_vm_init(&vm, ram, TA_MOD_RAM_SIZE, mod_ecall, TA_NULL); + status = TA_VM_RUNNING; + exit_code = 0; + loaded = ta_true; + ta_mod_running = loaded && (status == TA_VM_RUNNING); + + return ta_true; +} + +ta_bool ta_mod_load_file(const char *path) { + ta_fs_file file = ta_read_file(path); + + if (!file.data) { + return ta_false; + } + + ta_bool ok = ta_mod_load((const ta_uint8 *)file.data, file.size); + + ta_close_file(&file); + + return ok; +} + +ta_bool ta_mod_update(void) { + if (!loaded || status != TA_VM_RUNNING) { + return ta_false; + } + + status = ta_vm_run(&vm, TA_MOD_STEPS_PER_FRAME); + ta_mod_running = loaded && (status == TA_VM_RUNNING); + + return ta_mod_running; +} + +ta_vm_status ta_mod_status(void) { + return status; +} + +ta_int32 ta_mod_exit_code(void) { + return exit_code; +} + +const ta_vm *ta_mod_vm(void) { + return &vm; +} + +void ta_mod_unload(void) { + if (ram) { + ta_free(ram); + ram = TA_NULL; + } + + loaded = ta_false; + status = TA_VM_HALTED; + ta_mod_running = ta_false; +} diff --git a/src/ta_vm.c b/src/ta_vm.c @@ -0,0 +1,441 @@ +#include "ta_vm.h" + +#define OP_LOAD 0x03 +#define OP_MISC_MEM 0x0F +#define OP_OP_IMM 0x13 +#define OP_AUIPC 0x17 +#define OP_STORE 0x23 +#define OP_OP 0x33 +#define OP_LUI 0x37 +#define OP_BRANCH 0x63 +#define OP_JALR 0x67 +#define OP_JAL 0x6F +#define OP_SYSTEM 0x73 + +static ta_int32 sign_extend(ta_uint32 value, ta_uint32 bits) { + ta_uint32 mask = (ta_uint32)1 << (bits - 1); + + return (ta_int32)((value ^ mask) - mask); +} + +static ta_uint32 shift_right_arith(ta_uint32 value, ta_uint32 amount) { + ta_uint32 result = value >> amount; + + if (amount != 0 && (value & 0x80000000u) != 0) { + result |= ~(0xFFFFFFFFu >> amount); + } + + return result; +} + +static ta_uint32 mul_high_unsigned(ta_uint32 a, ta_uint32 b) { + ta_uint32 a_lo = a & 0xFFFFu; + ta_uint32 a_hi = a >> 16; + ta_uint32 b_lo = b & 0xFFFFu; + ta_uint32 b_hi = b >> 16; + ta_uint32 lo_lo = a_lo * b_lo; + ta_uint32 hi_lo = a_hi * b_lo; + ta_uint32 lo_hi = a_lo * b_hi; + ta_uint32 hi_hi = a_hi * b_hi; + ta_uint32 cross = (lo_lo >> 16) + (hi_lo & 0xFFFFu) + lo_hi; + + return hi_hi + (hi_lo >> 16) + (cross >> 16); +} + +static ta_uint32 mul_high_signed(ta_uint32 a, ta_uint32 b) { + ta_uint32 result = mul_high_unsigned(a, b); + + if (a & 0x80000000u) { + result -= b; + } + + if (b & 0x80000000u) { + result -= a; + } + + return result; +} + +static ta_uint32 mul_high_signed_unsigned(ta_uint32 a, ta_uint32 b) { + ta_uint32 result = mul_high_unsigned(a, b); + + if (a & 0x80000000u) { + result -= b; + } + + return result; +} + +static ta_uint32 div_signed(ta_uint32 a, ta_uint32 b) { + if (b == 0) { + return 0xFFFFFFFFu; + } + + if (a == 0x80000000u && b == 0xFFFFFFFFu) { + return a; + } + + return (ta_uint32)((ta_int32)a / (ta_int32)b); +} + +static ta_uint32 rem_signed(ta_uint32 a, ta_uint32 b) { + if (b == 0) { + return a; + } + + if (a == 0x80000000u && b == 0xFFFFFFFFu) { + return 0; + } + + return (ta_uint32)((ta_int32)a % (ta_int32)b); +} + +static ta_bool in_bounds(const ta_vm *vm, ta_uint32 addr, ta_uint32 size) { + return size <= vm->mem_size && addr <= vm->mem_size - size; +} + +ta_bool ta_vm_read8(const ta_vm *vm, ta_uint32 addr, ta_uint8 *out) { + if (!in_bounds(vm, addr, 1)) { + return ta_false; + } + + *out = vm->mem[addr]; + + return ta_true; +} + +ta_bool ta_vm_read32(const ta_vm *vm, ta_uint32 addr, ta_uint32 *out) { + if (!in_bounds(vm, addr, 4)) { + return ta_false; + } + + const ta_uint8 *p = vm->mem + addr; + + *out = (ta_uint32)p[0] | ((ta_uint32)p[1] << 8) | ((ta_uint32)p[2] << 16) | ((ta_uint32)p[3] << 24); + + return ta_true; +} + +ta_bool ta_vm_write32(ta_vm *vm, ta_uint32 addr, ta_uint32 value) { + if (!in_bounds(vm, addr, 4)) { + return ta_false; + } + + ta_uint8 *p = vm->mem + addr; + + p[0] = (ta_uint8)value; + p[1] = (ta_uint8)(value >> 8); + p[2] = (ta_uint8)(value >> 16); + p[3] = (ta_uint8)(value >> 24); + + return ta_true; +} + +static ta_bool load_value(const ta_vm *vm, ta_uint32 addr, ta_uint32 size, ta_uint32 *out) { + if (!in_bounds(vm, addr, size)) { + return ta_false; + } + + const ta_uint8 *p = vm->mem + addr; + ta_uint32 value = 0; + + for (ta_uint32 i = 0; i < size; i++) { + value |= (ta_uint32)p[i] << (i * 8); + } + + *out = value; + + return ta_true; +} + +static ta_bool store_value(ta_vm *vm, ta_uint32 addr, ta_uint32 size, ta_uint32 value) { + if (!in_bounds(vm, addr, size)) { + return ta_false; + } + + ta_uint8 *p = vm->mem + addr; + + for (ta_uint32 i = 0; i < size; i++) { + p[i] = (ta_uint8)(value >> (i * 8)); + } + + return ta_true; +} + +void ta_vm_init(ta_vm *vm, ta_uint8 *mem, ta_uint32 mem_size, ta_vm_ecall ecall, void *user) { + vm->mem = mem; + vm->mem_size = mem_size; + vm->ecall = ecall; + vm->user = user; + ta_vm_reset(vm, 0, mem_size); +} + +void ta_vm_reset(ta_vm *vm, ta_uint32 entry, ta_uint32 stack_top) { + for (ta_uint32 i = 0; i < 32; i++) { + vm->regs[i] = 0; + } + + vm->regs[TA_VM_REG_SP] = stack_top & ~(ta_uint32)15; + vm->pc = entry; + vm->steps = 0; +} + +ta_vm_status ta_vm_run(ta_vm *vm, ta_uint32 max_steps) { + ta_uint32 *x = vm->regs; + ta_uint32 pc = vm->pc; + ta_uint32 steps = 0; + ta_vm_status status = TA_VM_RUNNING; + + while (steps < max_steps) { + ta_uint32 inst; + + if ((pc & 3) != 0) { + status = TA_VM_FAULT_MISALIGNED; + break; + } + + if (!ta_vm_read32(vm, pc, &inst)) { + status = TA_VM_FAULT_MEMORY; + break; + } + + ta_uint32 opcode = inst & 0x7F; + ta_uint32 rd = (inst >> 7) & 31; + ta_uint32 funct3 = (inst >> 12) & 7; + ta_uint32 rs1 = (inst >> 15) & 31; + ta_uint32 rs2 = (inst >> 20) & 31; + ta_uint32 funct7 = inst >> 25; + ta_uint32 a = x[rs1]; + ta_uint32 b = x[rs2]; + ta_uint32 next = pc + 4; + ta_uint32 result = 0; + ta_bool write = ta_false; + + switch (opcode) { + case OP_LUI: { + result = inst & 0xFFFFF000u; + write = ta_true; + break; + } + case OP_AUIPC: { + result = pc + (inst & 0xFFFFF000u); + write = ta_true; + break; + } + case OP_JAL: { + ta_uint32 imm = ((inst >> 31) << 20) | (((inst >> 12) & 0xFF) << 12) | (((inst >> 20) & 1) << 11) | (((inst >> 21) & 0x3FF) << 1); + + result = next; + write = ta_true; + next = pc + (ta_uint32)sign_extend(imm, 21); + break; + } + case OP_JALR: { + if (funct3 != 0) { + status = TA_VM_FAULT_ILLEGAL; + break; + } + + result = next; + write = ta_true; + next = (a + (ta_uint32)sign_extend(inst >> 20, 12)) & ~(ta_uint32)1; + break; + } + case OP_BRANCH: { + ta_uint32 imm = ((inst >> 31) << 12) | (((inst >> 7) & 1) << 11) | (((inst >> 25) & 0x3F) << 5) | (((inst >> 8) & 0xF) << 1); + ta_bool taken = ta_false; + + switch (funct3) { + case 0: taken = a == b; break; + case 1: taken = a != b; break; + case 4: taken = (ta_int32)a < (ta_int32)b; break; + case 5: taken = (ta_int32)a >= (ta_int32)b; break; + case 6: taken = a < b; break; + case 7: taken = a >= b; break; + default: status = TA_VM_FAULT_ILLEGAL; break; + } + + if (taken) { + next = pc + (ta_uint32)sign_extend(imm, 13); + } + + break; + } + case OP_LOAD: { + ta_uint32 addr = a + (ta_uint32)sign_extend(inst >> 20, 12); + ta_uint32 value = 0; + ta_uint32 size = 0; + + switch (funct3) { + case 0: case 4: size = 1; break; + case 1: case 5: size = 2; break; + case 2: size = 4; break; + default: status = TA_VM_FAULT_ILLEGAL; break; + } + + if (status != TA_VM_RUNNING) { + break; + } + + if (!load_value(vm, addr, size, &value)) { + status = TA_VM_FAULT_MEMORY; + break; + } + + if (funct3 == 0) { + value = (ta_uint32)sign_extend(value, 8); + } else if (funct3 == 1) { + value = (ta_uint32)sign_extend(value, 16); + } + + result = value; + write = ta_true; + break; + } + case OP_STORE: { + ta_uint32 imm = ((inst >> 25) << 5) | ((inst >> 7) & 31); + ta_uint32 addr = a + (ta_uint32)sign_extend(imm, 12); + ta_uint32 size = 0; + + switch (funct3) { + case 0: size = 1; break; + case 1: size = 2; break; + case 2: size = 4; break; + default: status = TA_VM_FAULT_ILLEGAL; break; + } + + if (status != TA_VM_RUNNING) { + break; + } + + if (!store_value(vm, addr, size, b)) { + status = TA_VM_FAULT_MEMORY; + } + + break; + } + case OP_OP_IMM: { + ta_uint32 imm = (ta_uint32)sign_extend(inst >> 20, 12); + ta_uint32 shamt = rs2; + + switch (funct3) { + case 0: result = a + imm; break; + case 2: result = (ta_int32)a < (ta_int32)imm; break; + case 3: result = a < imm; break; + case 4: result = a ^ imm; break; + case 6: result = a | imm; break; + case 7: result = a & imm; break; + case 1: { + if (funct7 != 0x00) { + status = TA_VM_FAULT_ILLEGAL; + } + + result = a << shamt; + break; + } + default: { + if (funct7 == 0x00) { + result = a >> shamt; + } else if (funct7 == 0x20) { + result = shift_right_arith(a, shamt); + } else { + status = TA_VM_FAULT_ILLEGAL; + } + + break; + } + } + + write = ta_true; + break; + } + case OP_OP: { + if (funct7 == 0x01) { + switch (funct3) { + case 0: result = a * b; break; + case 1: result = mul_high_signed(a, b); break; + case 2: result = mul_high_signed_unsigned(a, b); break; + case 3: result = mul_high_unsigned(a, b); break; + case 4: result = div_signed(a, b); break; + case 5: result = b == 0 ? 0xFFFFFFFFu : a / b; break; + case 6: result = rem_signed(a, b); break; + default: result = b == 0 ? a : a % b; break; + } + } else if (funct7 == 0x00 || funct7 == 0x20) { + ta_bool alt = funct7 == 0x20; + + if (alt && funct3 != 0 && funct3 != 5) { + status = TA_VM_FAULT_ILLEGAL; + break; + } + + switch (funct3) { + case 0: result = alt ? a - b : a + b; break; + case 1: result = a << (b & 31); break; + case 2: result = (ta_int32)a < (ta_int32)b; break; + case 3: result = a < b; break; + case 4: result = a ^ b; break; + case 5: result = alt ? shift_right_arith(a, b & 31) : a >> (b & 31); break; + case 6: result = a | b; break; + default: result = a & b; break; + } + } else { + status = TA_VM_FAULT_ILLEGAL; + break; + } + + write = ta_true; + break; + } + case OP_MISC_MEM: { + break; + } + case OP_SYSTEM: { + ta_uint32 imm = inst >> 20; + + if (funct3 != 0 || rd != 0 || rs1 != 0) { + status = TA_VM_FAULT_ILLEGAL; + } else if (imm == 0) { + vm->pc = pc; + + if (vm->ecall) { + status = vm->ecall(vm, vm->user); + } else { + status = TA_VM_FAULT_ILLEGAL; + } + } else if (imm == 1) { + status = TA_VM_FAULT_EBREAK; + } else { + status = TA_VM_FAULT_ILLEGAL; + } + + break; + } + default: { + status = TA_VM_FAULT_ILLEGAL; + break; + } + } + + if (status != TA_VM_RUNNING && status != TA_VM_HALTED) { + break; + } + + if (write && rd != 0) { + x[rd] = result; + } + + x[0] = 0; + pc = next; + steps++; + + if (status != TA_VM_RUNNING) { + break; + } + } + + vm->pc = pc; + vm->steps = steps; + + return status; +}