Merge branch 'master' of ssh://git@git.sr.ht/~h3rald/xyw
jump to
@@ -0,0 +1,2 @@
+2026-07-27: +280D # Initial release. +2026-07-28: +3007 # Improved error management and debugging.
@@ -0,0 +1,21 @@
+## Changelog + +### v300-7 — 2026-07-28 + +#### Breaking Changes + +- Added more error codes and changed existing error code values. +- Debug output now written to stderr + +#### New Features + +- Enhanced debug messages +- Added more checks to avoid memory overflows. + +### v280-D — 2026-07-27 + +Initial release, featuring: + +- Integrated assembler for the xyw assembly language. +- Integrated virtual machine able to execute previously-assembled xim image files. +- Support for system, terminal, clock, file and beeper devices.
@@ -1,5 +0,0 @@
-This is the repository of the source code for the xyw virtual machine. - --> See https://xyw.2c.fyi for more information. - -
@@ -0,0 +1,6 @@
+This is the repository of the source code for the xyw virtual machine and assembler. + + +For more information and documentation, see <https://xyw.2c.fyi>. + +
@@ -0,0 +1,7 @@
+This project uses the Convergent Versioning system. + +The version number is a 2-byte hexadecimal integer from 0x0000 to 0xFFFF, with the first three digitsindicating the project _dependability score_ and the last digit encoding the version impact, compatibility, and purpose. + +A version number compliant with Semantic Versioning can be derived from the project history, and will still be used if required by package managers or similar software. + +For more information, see the [Convergent Versioning specification](https://h3rald.com/conver).
@@ -81,9 +81,43 @@ return remaining_ticks;
#endif } -void clock_setup(xyw_byte *data) +#define CLOCK_POLL_SLEEP_MS 100 + +int clock_poll(xyw_byte *data) +{ + xyw_word on_timer_handler = XYW_PEEKW(&data[CLOCK_ON_TIMER_ELAPSED]); + if (!on_timer_handler) + return 0; // no handler registered + + // Check if timer has elapsed (non-blocking) + clock_get_timer_value(); + if (clock_timer_elapsed_pending) + { + clock_timer_elapsed_pending = 0; + xyw_vm_set_running(1); + xyw_vm_push_frame(XYW_DEVICE_AREA_START); + *pc = on_timer_handler; + return 1; + } + + if (!clock_timer_active) + return 0; // no timer running + + // Timer is active but hasn't elapsed yet — sleep briefly to avoid busy loop. + // When terminal_poll is also active, it provides its own 100ms sleep via + // getch_timeout, so this sleep only matters for timer-only programs. +#ifdef _WIN32 + Sleep(CLOCK_POLL_SLEEP_MS); +#else + usleep((useconds_t)(CLOCK_POLL_SLEEP_MS * 1000)); +#endif + return -1; // active handler, no event yet +} + +void clock_setup(xyw_byte *data, xyw_byte *error) { (void)data; + (void)error; clock_timer_active = 0; clock_timer_elapsed_pending = 0; #ifdef _WIN32
@@ -1,5 +1,6 @@
#include "../xyw.h" -void clock_setup(xyw_byte *data); +void clock_setup(xyw_byte *data, xyw_byte *error); xyw_byte clock_input(xyw_byte *data, xyw_byte addr, xyw_byte *error); void clock_output(xyw_byte *data, xyw_byte addr, xyw_byte *error); +int clock_poll(xyw_byte *data);
@@ -1,5 +1,4 @@
#include <stdio.h> -#include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <errno.h>@@ -15,8 +14,6 @@ #define PATH_SEP '/'
#endif #include "../xyw.h" - -extern xyw_byte xyw_memory[]; // file device #define FILE_PATH 0x0@@ -33,6 +30,8 @@ #define FILE_OP_READ 0x01
#define FILE_OP_WRITE 0x02 #define FILE_OP_APPEND 0x03 #define FILE_OP_DELETE 0x04 +#define FILE_OP_COPY 0x05 +#define FILE_OP_MOVE 0x06 // File types #define XYW_FILE_TYPE_UNKNOWN 0x00@@ -44,6 +43,8 @@ #define FILE_ERROR_NOT_FOUND 0x31
#define FILE_ERROR_ACCESS_DENIED 0x32 #define FILE_ERROR_INVALID_OP 0x33 #define FILE_ERROR_IO_ERROR 0x34 +#define FILE_ERROR_PATH_TOO_LONG 0x35 +#define FILE_ERROR_BUFFER_OUT_OF_RANGE 0x36 // Maximum path length #define FILE_MAX_PATH 256@@ -54,8 +55,8 @@ static char file_current_path[FILE_MAX_PATH] = {0};
static long file_cursor = 0; static int file_is_write_mode = 0; -// Get null-terminated string from memory at given address -static void get_path_string(xyw_word addr, char *buf, size_t bufsize) +// Get null-terminated string from memory at given address. +static int get_path_string(xyw_word addr, char *buf, size_t bufsize) { size_t i; for (i = 0; i < bufsize - 1 && xyw_memory[addr + i] != 0; i++)@@ -63,6 +64,13 @@ {
buf[i] = xyw_memory[addr + i]; } buf[i] = '\0'; + return (xyw_memory[addr + i] != 0) ? -1 : 0; +} + +// Returns 1 if [addr, addr+length) fits entirely within xyw_memory, 0 otherwise. +static int file_buffer_in_range(xyw_word addr, xyw_word length) +{ + return ((unsigned int)addr + (unsigned int)length) <= XYW_MEMORY_SIZE; } // Close any open file@@ -79,7 +87,7 @@ file_is_write_mode = 0;
} // Update file type and size based on path -static void update_file_stats(xyw_byte *data) +static void update_file_stats(xyw_byte *data, xyw_byte *error) { xyw_word path_addr = XYW_PEEKW(&data[FILE_PATH]); char path[FILE_MAX_PATH];@@ -92,7 +100,13 @@ XYW_POKEW(&data[FILE_SIZE], 0);
return; } - get_path_string(path_addr, path, FILE_MAX_PATH); + if (get_path_string(path_addr, path, FILE_MAX_PATH) != 0) + { + *error = FILE_ERROR_PATH_TOO_LONG; + data[FILE_TYPE] = XYW_FILE_TYPE_UNKNOWN; + XYW_POKEW(&data[FILE_SIZE], 0); + return; + } if (stat(path, &st) != 0) {@@ -138,7 +152,18 @@ *error = FILE_ERROR_INVALID_OP;
return; } - get_path_string(path_addr, path, FILE_MAX_PATH); + if (!file_buffer_in_range(buffer_addr, length)) + { + XYW_DBG(" Buffer out of range for file_read: addr=%04X length=%04X\n", buffer_addr, length); + *error = FILE_ERROR_BUFFER_OUT_OF_RANGE; + return; + } + + if (get_path_string(path_addr, path, FILE_MAX_PATH) != 0) + { + *error = FILE_ERROR_PATH_TOO_LONG; + return; + } XYW_DBG(" Reading file: %s, length=%u, cursor=%ld\n", path, length, file_cursor);@@ -341,7 +366,18 @@ *error = FILE_ERROR_INVALID_OP;
return; } - get_path_string(path_addr, path, FILE_MAX_PATH); + if (!file_buffer_in_range(buffer_addr, length)) + { + XYW_DBG(" Buffer out of range for file_write: addr=%04X length=%04X\n", buffer_addr, length); + *error = FILE_ERROR_BUFFER_OUT_OF_RANGE; + return; + } + + if (get_path_string(path_addr, path, FILE_MAX_PATH) != 0) + { + *error = FILE_ERROR_PATH_TOO_LONG; + return; + } XYW_DBG(" Writing to file: %s (append=%d), length=%u, cursor=%ld\n", path, append, length, file_cursor);@@ -442,7 +478,11 @@ *error = FILE_ERROR_INVALID_OP;
return; } - get_path_string(path_addr, path, FILE_MAX_PATH); + if (get_path_string(path_addr, path, FILE_MAX_PATH) != 0) + { + *error = FILE_ERROR_PATH_TOO_LONG; + return; + } // Close file if it's the one being deleted if (file_handle && strcmp(file_current_path, path) == 0)@@ -463,11 +503,128 @@ // Success = 1 for successful delete
XYW_POKEW(&data[FILE_SUCCESS], 1); // Update stats after delete - update_file_stats(data); + update_file_stats(data, error); +} + +// Parse "source|destination" from path string +static int split_path_pair(xyw_byte *data, char *src, char *dst, size_t bufsize) +{ + xyw_word path_addr = XYW_PEEKW(&data[FILE_PATH]); + char combined[FILE_MAX_PATH * 2]; + + if (path_addr == 0) + return -1; + + if (get_path_string(path_addr, combined, sizeof(combined)) != 0) + { + return -1; + } + + char *sep = strchr(combined, '|'); + if (!sep) + return -1; + + *sep = '\0'; + strncpy(src, combined, bufsize - 1); + src[bufsize - 1] = '\0'; + strncpy(dst, sep + 1, bufsize - 1); + dst[bufsize - 1] = '\0'; + return 0; +} + +// Copy a file +static void file_copy(xyw_byte *data, xyw_byte *error) +{ + char src[FILE_MAX_PATH], dst[FILE_MAX_PATH]; + + XYW_POKEW(&data[FILE_SUCCESS], 0); + + if (split_path_pair(data, src, dst, FILE_MAX_PATH) != 0) + { + *error = FILE_ERROR_INVALID_OP; + return; + } + + // Close if source or dest is currently open + if (file_handle && (strcmp(file_current_path, src) == 0 || strcmp(file_current_path, dst) == 0)) + file_close(); + + XYW_DBG(" Copying: %s -> %s\n", src, dst); + + FILE *in = fopen(src, "rb"); + if (!in) + { + *error = FILE_ERROR_NOT_FOUND; + return; + } + + FILE *out = fopen(dst, "wb"); + if (!out) + { + fclose(in); + *error = FILE_ERROR_ACCESS_DENIED; + return; + } + + char buf[4096]; + size_t total = 0; + size_t n; + while ((n = fread(buf, 1, sizeof(buf), in)) > 0) + { + if (fwrite(buf, 1, n, out) != n) + { + fclose(in); + fclose(out); + *error = FILE_ERROR_IO_ERROR; + return; + } + total += n; + } + + fclose(in); + fclose(out); + + XYW_POKEW(&data[FILE_SUCCESS], (xyw_word)(total > 0xFFFF ? 0xFFFF : total)); } -void file_setup(xyw_byte *data) +// Move (rename) a file +static void file_move(xyw_byte *data, xyw_byte *error) +{ + char src[FILE_MAX_PATH], dst[FILE_MAX_PATH]; + + XYW_POKEW(&data[FILE_SUCCESS], 0); + + if (split_path_pair(data, src, dst, FILE_MAX_PATH) != 0) + { + *error = FILE_ERROR_INVALID_OP; + return; + } + + // Close if source or dest is currently open + if (file_handle && (strcmp(file_current_path, src) == 0 || strcmp(file_current_path, dst) == 0)) + file_close(); + + XYW_DBG(" Moving: %s -> %s\n", src, dst); + + if (rename(src, dst) != 0) + { + // rename may fail across filesystems; fall back to copy+delete + file_copy(data, error); + if (*error != 0) + return; + if (remove(src) != 0) + { + *error = FILE_ERROR_IO_ERROR; + return; + } + } + + XYW_POKEW(&data[FILE_SUCCESS], 1); +} + +void file_setup(xyw_byte *data, xyw_byte *error) { + (void)error; // Close any open file file_close();@@ -488,7 +645,7 @@ once for each byte), close current file, update stats, reset cursor */
if (addr == FILE_PATH + 1) { file_close(); - update_file_stats(data); + update_file_stats(data, error); } // When operation is written, execute it@@ -509,6 +666,12 @@ file_write_op(data, error, 1);
break; case FILE_OP_DELETE: file_delete(data, error); + break; + case FILE_OP_COPY: + file_copy(data, error); + break; + case FILE_OP_MOVE: + file_move(data, error); break; default: if (op != 0)
@@ -1,5 +1,5 @@
#include "../xyw.h" -void file_setup(xyw_byte *data); +void file_setup(xyw_byte *data, xyw_byte *error); xyw_byte file_input(xyw_byte *data, xyw_byte addr, xyw_byte *error); void file_output(xyw_byte *data, xyw_byte addr, xyw_byte *error);
@@ -1,18 +1,36 @@
#include <time.h> #include <stdlib.h> #include <stdio.h> +#include <string.h> #include "../xyw.h" #define SYSTEM_STATE 0x00 #define SYSTEM_ERROR 0x01 #define SYSTEM_PAGE 0x02 #define SYSTEM_RANDOM 0x03 +#define SYSTEM_ON_ERROR 0x04 #define SYSTEM_IMAGE_EXEC 0x06 -void system_setup(xyw_byte *data) +#define MAX_IMAGE_EXEC_DEPTH 8 +#define MAX_IMAGE_EXEC_PATH_LENGTH 256 + +/// Error handler tracking +static xyw_byte error_handler_entry_s = 0; +static int error_handler_armed = 0; + +/// Exec stack management +static xyw_vm_snapshot exec_stack[MAX_IMAGE_EXEC_DEPTH]; +static int exec_stack_depth = 0; +static int exec_pending = 0; +static char exec_path_buf[MAX_IMAGE_EXEC_PATH_LENGTH]; + +void system_setup(xyw_byte *data, xyw_byte *error) { (void)data; + (void)error; srand(time(NULL)); + error_handler_entry_s = 0; + error_handler_armed = 0; } xyw_byte system_input(xyw_byte *data, xyw_byte addr, xyw_byte *error)@@ -23,6 +41,20 @@ (void)error;
return data[addr]; } +static void get_string_from_memory(xyw_word addr, char *buf, size_t bufsize) +{ + size_t i; + for (i = 0; i < bufsize - 1 && xyw_memory[addr + i] != 0; i++) + buf[i] = xyw_memory[addr + i]; + buf[i] = '\0'; +} + +static void request_exec(xyw_word path_addr) +{ + get_string_from_memory(path_addr, exec_path_buf, sizeof(exec_path_buf)); + exec_pending = 1; +} + void system_output(xyw_byte *data, xyw_byte addr, xyw_byte *error) { (void)error;@@ -30,6 +62,108 @@ if (addr == SYSTEM_IMAGE_EXEC + 1)
{ xyw_word target = XYW_PEEKW(&data[SYSTEM_IMAGE_EXEC]); if (target != 0) - xyw_request_exec(target); + request_exec(target); + } +} + +static void split_exec_string(char *full, char **path, char **argstart) +{ + char *sp = strchr(full, ' '); + if (sp) + { + *sp = '\0'; + *argstart = sp + 1; } + else + { + *argstart = NULL; + } + *path = full; +} + +static void handle_exec_requests(xyw_byte *data) +{ + if (!exec_pending) + return; + exec_pending = 0; + + if (exec_stack_depth >= MAX_IMAGE_EXEC_DEPTH) + { + data[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_EXEC_DEPTH_EXCEEDED; + return; + } + xyw_vm_save_snapshot(&exec_stack[exec_stack_depth++]); + + char path_copy[MAX_IMAGE_EXEC_PATH_LENGTH]; + strncpy(path_copy, exec_path_buf, sizeof(path_copy) - 1); + path_copy[sizeof(path_copy) - 1] = '\0'; + char *path, *argstart; + split_exec_string(path_copy, &path, &argstart); + + xyw_vm_reset(); + if (xyw_vm_load_image(path, argstart) != 0) + { + // Restore parent state so error handler can run in the caller's context + xyw_vm_restore_snapshot(&exec_stack[--exec_stack_depth]); + } +} + +void system_tick(xyw_byte *data) +{ + // Check if returning from error handler (after RTS popped the frame) + if ((data[SYSTEM_STATE] & XYW_STATE_ERROR) && + error_handler_armed && + xyw_vm_get_system_depth() == error_handler_entry_s) + { + data[SYSTEM_STATE] &= ~XYW_STATE_ERROR; + error_handler_entry_s = 0; + error_handler_armed = 0; + } + + // Check for new error condition + if (data[SYSTEM_ERROR] != XYW_SYSTEM_ERROR_NONE && !(data[SYSTEM_STATE] & XYW_STATE_ERROR)) + { + xyw_word on_error_handler = XYW_PEEKW(&data[SYSTEM_ON_ERROR]); + if (on_error_handler != 0) + { + XYW_DBG("Error occurred: %02X, jumping to error handler at %04X\n", data[SYSTEM_ERROR], on_error_handler); + data[SYSTEM_STATE] |= XYW_STATE_ERROR; + error_handler_armed = 1; + error_handler_entry_s = xyw_vm_get_system_depth(); + xyw_vm_push_frame(*pc); + *pc = on_error_handler; + } + else + { + // No error handler: halt execution + data[SYSTEM_STATE] &= ~XYW_STATE_RUNNING; + } + } + + // Handle exec requests + handle_exec_requests(data); +} + +void system_save(xyw_byte *data, xyw_byte *state) +{ + (void)data; + state[0] = error_handler_entry_s; + state[1] = (xyw_byte)error_handler_armed; +} + +void system_restore(xyw_byte *data, const xyw_byte *state) +{ + (void)data; + error_handler_entry_s = state[0]; + error_handler_armed = state[1]; +} + +int xyw_system_try_restore_parent(void) +{ + if (exec_stack_depth > 0) + { + xyw_vm_restore_snapshot(&exec_stack[--exec_stack_depth]); + return 1; + } + return 0; }
@@ -1,5 +1,8 @@
#include "../xyw.h" -void system_setup(xyw_byte *data); +void system_setup(xyw_byte *data, xyw_byte *error); xyw_byte system_input(xyw_byte *data, xyw_byte addr, xyw_byte *error); -void system_output(xyw_byte *data, xyw_byte addr, xyw_byte *error);+void system_output(xyw_byte *data, xyw_byte addr, xyw_byte *error); +void system_tick(xyw_byte *data); +void system_save(xyw_byte *data, xyw_byte *state); +void system_restore(xyw_byte *data, const xyw_byte *state);
@@ -1,5 +1,6 @@
#include <stdio.h> -#include "../xyw.h" +#include <string.h> +#include "terminal.h" #ifndef _WIN32 #include <termios.h>@@ -15,33 +16,172 @@ #define TERMINAL_OUTPUT 0x1
#define TERMINAL_ON_KEYPRESS 0x2 #define TERMINAL_ON_ARGUMENT 0x4 -void terminal_init(xyw_byte *data) +#define TERMINAL_POLL_INTERVAL_MS 100 +#define TERMINAL_ERROR_NOT_A_TTY 0x11 + +//// Platform-specific non-blocking key input + +#ifdef _WIN32 +#include <conio.h> +#include <windows.h> +static int getch_timeout(int timeout_ms) +{ + HANDLE h = GetStdHandle(STD_INPUT_HANDLE); + if (WaitForSingleObject(h, timeout_ms) == WAIT_OBJECT_0 && _kbhit()) + return _getch(); + return 0; +} +#else +static int getch_timeout(int timeout_ms) +{ + (void)timeout_ms; // bounded by VMIN=0/VTIME in terminal_setup() + int c = getchar(); + return (c == EOF) ? 0 : c; +} +#endif + +//// Argument feeding state + +static int arg_index = 1; +static int arg_char_pos = 0; +static int exec_args_pos = 0; + +//// Generic save/restore + +void terminal_save(xyw_byte *data, xyw_byte *state) { (void)data; + memcpy(&state[0], &arg_index, sizeof(arg_index)); + memcpy(&state[4], &arg_char_pos, sizeof(arg_char_pos)); + memcpy(&state[8], &exec_args_pos, sizeof(exec_args_pos)); +} + +void terminal_restore(xyw_byte *data, const xyw_byte *state) +{ + (void)data; + memcpy(&arg_index, &state[0], sizeof(arg_index)); + memcpy(&arg_char_pos, &state[4], sizeof(arg_char_pos)); + memcpy(&exec_args_pos, &state[8], sizeof(exec_args_pos)); +} + +//// Poll: argument feeding and keypress handling + +int terminal_poll(xyw_byte *data) +{ + // Feed command line arguments + xyw_word on_arg_handler = XYW_PEEKW(&data[TERMINAL_ON_ARGUMENT]); + if (!xyw_argv_processed && on_arg_handler) + { + if (xyw_exec_args_active) + { + char c = xyw_exec_args[exec_args_pos]; + if (c == ' ') + { + data[TERMINAL_INPUT] = XYW_ARGUMENT_SEPARATOR; + exec_args_pos++; + } + else if (c != '\0') + { + data[TERMINAL_INPUT] = (xyw_byte)c; + exec_args_pos++; + } + else + { + data[TERMINAL_INPUT] = XYW_END_OF_ARGUMENTS; + xyw_argv_processed = 1; + xyw_exec_args_active = 0; + } + } + else if (arg_index < xyw_argc) + { + char c = xyw_argv[arg_index][arg_char_pos]; + if (c != '\0') + { + data[TERMINAL_INPUT] = (xyw_byte)c; + arg_char_pos++; + } + else if (arg_index < xyw_argc - 1) + { + data[TERMINAL_INPUT] = XYW_ARGUMENT_SEPARATOR; + arg_index++; + arg_char_pos = 0; + } + else + { + data[TERMINAL_INPUT] = XYW_END_OF_ARGUMENTS; + xyw_argv_processed = 1; + } + } + else + { + data[TERMINAL_INPUT] = XYW_END_OF_ARGUMENTS; + xyw_argv_processed = 1; + } + xyw_vm_set_running(1); + xyw_vm_push_frame(XYW_DEVICE_AREA_START); + *pc = on_arg_handler; + return 1; + } + + // Keypress handling + xyw_word on_keypress_handler = XYW_PEEKW(&data[TERMINAL_ON_KEYPRESS]); + if (on_keypress_handler) + { + int c = getch_timeout(TERMINAL_POLL_INTERVAL_MS); + if (c > 0) + { + if (c == 3 || c == 4) + { + xyw_vm_set_waiting(0); + return 0; + } + data[TERMINAL_INPUT] = (xyw_byte)c; + xyw_vm_set_running(1); + xyw_vm_push_frame(XYW_DEVICE_AREA_START); + *pc = on_keypress_handler; + return 1; + } + return -1; // active handler, no event yet (keep polling) + } + + return 0; +} + +//// Device init/teardown/IO + +void terminal_setup(xyw_byte *data, xyw_byte *error) +{ + (void)data; + (void)error; + // Reset arg feeding state for fresh image + arg_index = 1; + arg_char_pos = 0; + exec_args_pos = 0; #ifndef _WIN32 struct termios raw; if (!isatty(STDIN_FILENO)) { - // Not a real terminal (e.g. piped input/tests) — nothing to save or set + *error = TERMINAL_ERROR_NOT_A_TTY; return; } if (tcgetattr(STDIN_FILENO, &terminal_orig_termios) != 0) { XYW_DBG("terminal: tcgetattr failed, raw mode not enabled\n"); + *error = TERMINAL_ERROR_NOT_A_TTY; return; } raw = terminal_orig_termios; raw.c_lflag &= ~(ICANON | ECHO | ISIG); - // Non-blocking read: return after 100ms even if no input (for polling) raw.c_cc[VMIN] = 0; raw.c_cc[VTIME] = 1; if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) != 0) { XYW_DBG("terminal: tcsetattr failed, raw mode not enabled\n"); + *error = TERMINAL_ERROR_NOT_A_TTY; return; }
@@ -1,6 +1,9 @@
#include "../xyw.h" -void terminal_init(xyw_byte *data); +void terminal_setup(xyw_byte *data, xyw_byte *error); void terminal_output(xyw_byte *data, xyw_byte addr, xyw_byte *error); xyw_byte terminal_input(xyw_byte *data, xyw_byte addr, xyw_byte *error); -void terminal_teardown(xyw_byte *data);+void terminal_teardown(xyw_byte *data); +int terminal_poll(xyw_byte *data); +void terminal_save(xyw_byte *data, xyw_byte *state); +void terminal_restore(xyw_byte *data, const xyw_byte *state);
@@ -99,6 +99,7 @@
need_file "$EXAMPLES_DIR/$img_basename" local run_log="$TMPDIR/run.${img_basename}.log" + #if ! (cd "$EXAMPLES_DIR" && "$xyw_bin" "$img_basename" "${run_args[@]}" </dev/null >"$out_file" 2>"$run_log"); then if ! (cd "$EXAMPLES_DIR" && "$xyw_bin" "$img_basename" "${run_args[@]}" >"$out_file" 2>"$run_log"); then dump_file_if_exists "Runtime stderr" "$run_log" dump_file_if_exists "Runtime stdout" "$out_file"
@@ -29,6 +29,25 @@ return 1;
return 0; } +void print_help() { + unsigned int v[2]; + v[0] = (XYW_VERSION >> 4) & 0xFFF; + v[1] = XYW_VERSION & 0xF; + printf("xyw v%03X-%X - (c) 2026 Fabio Cevasco\n\n", v[0], v[1]); + printf("USAGE\n" + " xyw [options] <file>.[xyw|xim]\n" + "\n" + "ARGUMENTS\n" + " <file> The name of the file to process:\n" + " - a .xyw source file will be assembled into a .xim file.\n" + " - a .xim image file will be executed.\n" + "\n" + "OPTIONS\n" + " -d, --debug Enable debug mode.\n" + " -v, --version Display xyw version.\n" + " -h, --help Print this message.\n"); +} + int main(int argc, char *argv[]) { // First pass: detect flags@@ -38,20 +57,38 @@ if (flag(argv[i], "debug"))
{ xyw_debug = 1; } + if (flag(argv[i], "version")) + { + printf("%04X\n", XYW_VERSION); + return 0; + } + if (flag(argv[i], "help")) + { + print_help(); + return 0; + } + } // Second pass: copy non-flag args into xyw_argv xyw_argc = 0; for (int i = 0; i < argc && xyw_argc < 32; i++) { - if (i > 0 && flag(argv[i], "debug")) + if (i > 0 && (flag(argv[i], "debug") || flag(argv[i], "version") || flag(argv[i], "help"))) + { continue; + } xyw_argv[xyw_argc++] = argv[i]; } + if (argc <= 1) { + print_help(); + return 0; + } + for (int i = 1; i < argc; i++) { - if (flag(argv[i], "debug")) + if (flag(argv[i], "debug") || flag(argv[i], "version") || flag(argv[i], "help")) { continue; // already handled }
@@ -1,28 +1,57 @@
#ifndef XYW_H #define XYW_H +#define XYW_VERSION 0x3007 + +// Registers and memory #define XYW_MODE_X 0x80 #define XYW_MODE_Y 0x40 #define XYW_MODE_W 0x20 #define XYW_TOTAL_INSTRUCTIONS 0x20 #define XYW_TOTAL_DEVICES 0x10 +#define XYW_DEVICE_AREA_START 0xff00 #define XYW_ARGUMENT_SEPARATOR 0x1E #define XYW_END_OF_ARGUMENTS 0x1D +// System state flags (architectural constants for SYSTEM_STATE register) +#define XYW_STATE_RUNNING 0x01 +#define XYW_STATE_WAITING 0x02 +#define XYW_STATE_DEBUG 0x40 +#define XYW_STATE_ERROR 0x80 + +// System Errors +#define XYW_SYSTEM_ERROR_NONE 0x00 +#define XYW_SYSTEM_ERROR_DIVISION_BY_ZERO 0x01 +#define XYW_SYSTEM_ERROR_USER_STACK_UNDERFLOW 0x02 +#define XYW_SYSTEM_ERROR_USER_STACK_OVERFLOW 0x03 +#define XYW_SYSTEM_ERROR_SYSTEM_STACK_UNDERFLOW 0x04 +#define XYW_SYSTEM_ERROR_SYSTEM_STACK_OVERFLOW 0x05 +#define XYW_SYSTEM_ERROR_EXEC_DEPTH_EXCEEDED 0x06 +#define XYW_SYSTEM_ERROR_IMAGE_NOT_FOUND 0x07 +#define XYW_SYSTEM_ERROR_IMAGE_READ_ERROR 0x08 +#define XYW_SYSTEM_ERROR_IMAGE_TOO_LARGE 0x09 +#define XYW_SYSTEM_ERROR_INVALID_OPCODE 0x0A + typedef unsigned char xyw_byte; typedef unsigned short xyw_word; // clang-format off #define XYW_PEEKW(d) (*(d) << 8 | (d)[1]) #define XYW_POKEW(d, v) { *(d) = (v) >> 8; (d)[1] = (v); } -#define XYW_DBG(...) do { if (xyw_debug) fprintf(stdout, __VA_ARGS__); } while(0) +#define XYW_DBG(...) do { if (xyw_debug) fprintf(stderr, __VA_ARGS__); } while(0) // clang-format on // Devices API typedef xyw_byte (*xyw_input)(xyw_byte *data, xyw_byte addr, xyw_byte *error); typedef void (*xyw_output)(xyw_byte *data, xyw_byte addr, xyw_byte *error); -typedef void (*xyw_setup)(xyw_byte *data); +typedef void (*xyw_setup)(xyw_byte *data, xyw_byte *error); typedef void (*xyw_teardown)(xyw_byte *data); +typedef int (*xyw_poll)(xyw_byte *data); // returns: 1=dispatched, 0=inactive, -1=active but no event +typedef void (*xyw_save)(xyw_byte *data, xyw_byte *state); +typedef void (*xyw_restore)(xyw_byte *data, const xyw_byte *state); +typedef void (*xyw_tick)(xyw_byte *data); + +#define XYW_DEVICE_STATE_SIZE 64 typedef struct xyw_device {@@ -31,6 +60,10 @@ xyw_setup setup;
xyw_input input; xyw_output output; xyw_teardown teardown; + xyw_poll poll; + xyw_save save; + xyw_restore restore; + xyw_tick tick; } xyw_device; // Main Globals@@ -40,11 +73,55 @@ extern int xyw_debug;
extern int xyw_argc; extern char *xyw_argv[]; extern int xyw_argv_processed; + +// Exec args: set by VM exec logic, read by terminal during poll +#define XYW_EXEC_ARGS_SIZE 256 +extern char xyw_exec_args[XYW_EXEC_ARGS_SIZE]; +extern int xyw_exec_args_active; // Main public API int xyw_assemble(const char *input, const char *output); int xyw_run(const char *input); int xyw_eval(xyw_word pc); -void xyw_request_exec(xyw_word path_addr); + +// Privileged VM API (for device implementations) +extern xyw_byte xyw_memory[]; +extern xyw_word *pc, *xw, *yw; +extern xyw_byte *x, *y; +extern xyw_byte *dev_error; + +void xyw_vm_push_frame(xyw_word return_addr); +xyw_word xyw_vm_pop_frame(void); +xyw_byte xyw_vm_get_system_depth(void); + +void xyw_vm_set_running(int running); +void xyw_vm_set_waiting(int waiting); +int xyw_vm_is_running(void); +int xyw_vm_is_waiting(void); +int xyw_vm_is_error_state(void); + +void xyw_vm_reset(void); + +// VM snapshot (for exec stack management by system device) +#define XYW_MEMORY_SIZE 0x10000 +#define XYW_USER_STACK_SIZE 128 +#define XYW_SYSTEM_STACK_SIZE 384 + +typedef struct { + xyw_byte memory[XYW_MEMORY_SIZE]; + xyw_byte user_stack[XYW_USER_STACK_SIZE]; + xyw_byte system_stack[XYW_SYSTEM_STACK_SIZE]; + xyw_byte s, u, x_val, y_val; + xyw_word pc_val, xw_val, yw_val; + int argv_processed; + char exec_args[XYW_EXEC_ARGS_SIZE]; + int exec_args_active; + xyw_byte device_state[XYW_TOTAL_DEVICES][XYW_DEVICE_STATE_SIZE]; +} xyw_vm_snapshot; + +void xyw_vm_save_snapshot(xyw_vm_snapshot *snap); +void xyw_vm_restore_snapshot(xyw_vm_snapshot *snap); +int xyw_vm_load_image(const char *path, const char *args); +int xyw_system_try_restore_parent(void); #endif // XYW_H
@@ -105,12 +105,15 @@ xyw_byte data[0x10000] = {};
// Current write pointer xyw_word ptr = 0; +static size_t bytes_written = 0; +static int bytecode_overflow = 0; // Flat single-string "dictionary" for symbols (macros and labels) // Removing 256 bytes to leave room for devices. // Actually following Uxn design here ;) char dict[0x8000 - 0x100]; char *dictnext = dict; +static int dict_overflow = 0; // List of symbols (macros and labels) xyw_symbol symbols[MAX_SYMBOLS];@@ -125,6 +128,17 @@
// Stores a new string in the dictionary and returns its address static char *dict_store(char *str) { + if (dict_overflow) + { + return dict; + } + + size_t len = strlen(str); + if ((size_t)(dictnext - dict) + len + 1 > sizeof(dict)) + { + dict_overflow = 1; + return dict; + } char *o = dictnext; while (*str) {@@ -262,6 +276,16 @@
// Append raw bytecode data static inline void append(xyw_word val) { + if (bytecode_overflow) + { + return; + } + unsigned int n = (val > 0xff) ? 2 : 1; + if (bytes_written + n > sizeof(data)) + { + bytecode_overflow = 1; + return; + } if (val > 0xff) { XYW_POKEW(&data[ptr], val);@@ -272,6 +296,44 @@ {
data[ptr] = (xyw_byte)val; } ptr++; + bytes_written += n; +} + +// Writes a single raw byte, with overflow check +static inline void store_byte(xyw_byte val) +{ + if (bytecode_overflow) + { + return; + } + + if (bytes_written + 1 > sizeof(data)) + { + bytecode_overflow = 1; + return; + } + + data[ptr++] = val; + bytes_written++; +} + +// Writes a single raw word, with overflow check +static inline void store_word(xyw_word val) +{ + if (bytecode_overflow) + { + return; + } + + if (bytes_written + 2 > sizeof(data)) + { + bytecode_overflow = 1; + return; + } + + XYW_POKEW(&data[ptr], val); + ptr += 2; + bytes_written += 2; } // Encode a push instruction@@ -422,13 +484,12 @@ // Use digit count to determine byte vs word, not value
if (token_is_word) { append(PSH | XYW_MODE_W); - XYW_POKEW(&data[ptr], value); - ptr += 2; + store_word(value); } else { append(PSH); - data[ptr++] = (xyw_byte)value; + store_byte((xyw_byte)value); } } return 0;@@ -561,8 +622,7 @@ {
XYW_DBG("Label Ref:\t%s -> %04X\n", token, sym->address); // Push label address append(PSH | XYW_MODE_W); - XYW_POKEW(&data[ptr], sym->address); - ptr += 2; + store_word(sym->address); } else if (sym->type == SYMBOL_TYPE_REF) {@@ -581,8 +641,8 @@ if (ref(ctx, token) != 0)
{ return -1; } - append(0x00); - append(0x00); + store_byte(0x00); + store_byte(0x00); } } return 0;@@ -644,9 +704,9 @@ XYW_DBG("Directive:\t.string %s\n", token);
// Store string in memory (without quotes) for (unsigned int i = 1; i < token_length - 1; i++) { - data[ptr++] = (xyw_byte)token[i]; + store_byte((xyw_byte)token[i]); } - data[ptr++] = 0; + store_byte(0); return validate_single_directive_argument(ctx, r); }@@ -692,12 +752,14 @@ }
if (token_is_word) { // Word value: emit high byte then low byte - data[ptr++] = (xyw_byte)(token_value >> 8); - data[ptr++] = (xyw_byte)(token_value & 0xFF); + store_byte((xyw_byte)(token_value >> 8)); + store_byte((xyw_byte)(token_value & 0xFF)); } else { data[ptr++] = (xyw_byte)token_value; + store_byte((xyw_byte)token_value); + } // Now break if we were at end of line/file if (r == TOKEN_END_EOL || r == TOKEN_END_EOF || r == TOKEN_END_COMMENT)@@ -1126,9 +1188,12 @@ ctx.column = 1;
// Reset all assembler state so xyw_assemble can be called multiple times memset(data, 0, sizeof(data)); + bytes_written = 0; + bytecode_overflow = 0; ptr = 0; memset(dict, 0, sizeof(dict)); dictnext = dict; + dict_overflow = 0; memset(symbols, 0, sizeof(symbols)); symcount = 0; memset(refs, 0, sizeof(refs));@@ -1145,6 +1210,19 @@ if (assemble_file(&ctx) != 0)
{ return -1; } + + if (bytecode_overflow) + { + fprintf(stderr, "Error - Assembled image exceeds %d-byte memory size [%s]\n", XYW_MEMORY_SIZE, input); + return -1; + } + + if (dict_overflow) + { + fprintf(stderr, "Error - Symbol dictionary full [%s]\n", input); + return -1; + } + FILE *fp = fopen(output, "wb"); if (!fp) {
@@ -1,6 +1,7 @@
#include <stdio.h> #include <stdlib.h> #include <string.h> +#include <signal.h> #include "xyw.h" #include "devices/system.h"@@ -9,76 +10,23 @@ #include "devices/clock.h"
#include "devices/file.h" #include "devices/beeper.h" -#define MEMORY_SIZE 0x10000 - -#define USER_STACK_SIZE 128 #define SYSTEM_FRAME_SIZE 8 #define SYSTEM_MAX_FRAMES 48 -#define USER_MEMORY_START 0x000 -#define DEVICE_AREA_START 0xff00 - -#define MAX_IMAGE_EXEC_DEPTH 8 -#define MAX_IMAGE_EXEC_PATH_LENGTH 256 - -#define POLL_INTERVAL_MS 100 - -/* system device addresses */ +/* system device addresses (architectural register locations) */ #define SYSTEM_STATE 0xff00 #define SYSTEM_ERROR 0xff01 #define SYSTEM_PAGE 0xff02 -#define SYSTEM_RANDOM 0xff03 -#define SYSTEM_ON_ERROR 0xff04 - -/* Terminal device addresses */ -#define TERMINAL_INPUT 0xff10 -#define TERMINAL_ON_KEYPRESS 0xff12 -#define TERMINAL_ON_ARGUMENT 0xff14 - -/* Clock device addresses */ -#define CLOCK_TIMER 0xff28 -#define CLOCK_ON_TIMER_ELAPSED 0xff2a - -#ifdef _WIN32 -#include <conio.h> -#include <windows.h> -int getch_timeout(int timeout_ms) -{ - HANDLE h = GetStdHandle(STD_INPUT_HANDLE); - if (WaitForSingleObject(h, timeout_ms) == WAIT_OBJECT_0 && _kbhit()) - return _getch(); - return 0; -} -#else -#include <unistd.h> -int getch_timeout(int timeout_ms) -{ - (void)timeout_ms; // bounded by VMIN=0/VTIME in terminal_init() - int c = getchar(); - return (c == EOF) ? 0 : c; -} -#endif -typedef enum -{ - XYW_ERROR_NONE = 0, - XYW_ERROR_DIVISION_BY_ZERO, - XYW_ERROR_STACK_UNDERFLOW, - XYW_ERROR_STACK_OVERFLOW, - XYW_ERROR_EXEC_DEPTH_EXCEEDED, - XYW_ERROR_IMAGE_NOT_FOUND, - XYW_ERROR_IMAGE_READ_ERROR, -} xyw_error; - -#define SYSTEM_STATE_RUNNING 0x01 -#define SYSTEM_STATE_WAITING 0x02 -#define SYSTEM_STATE_DEBUG 0x40 -#define SYSTEM_STATE_ERROR 0x80 /// Main memory -xyw_byte xyw_memory[MEMORY_SIZE]; +xyw_byte xyw_memory[XYW_MEMORY_SIZE]; + +/// Exec args (set by exec logic, consumed by terminal poll) +char xyw_exec_args[XYW_EXEC_ARGS_SIZE]; +int xyw_exec_args_active = 0; /// Stacks (not part of addressable memory) -static xyw_byte user_stack[USER_STACK_SIZE]; +static xyw_byte user_stack[XYW_USER_STACK_SIZE]; static xyw_byte system_stack[SYSTEM_MAX_FRAMES * SYSTEM_FRAME_SIZE]; static xyw_byte s = 0; // system stack frame index static xyw_byte u = 0; // user stack pointer@@ -103,9 +51,9 @@
//// Helpers to retrieve device number and address within the device static inline int get_device(xyw_word addr) { - if (addr < DEVICE_AREA_START) + if (addr < XYW_DEVICE_AREA_START) return -1; - return (addr - DEVICE_AREA_START) / 16; + return (addr - XYW_DEVICE_AREA_START) / 16; } static inline xyw_byte *get_device_data(xyw_word addr)@@ -116,7 +64,7 @@
static inline xyw_byte get_device_address(xyw_word addr) { // Given $FF41, it should return $01 - return (xyw_byte)((addr - DEVICE_AREA_START) & 0x0F); + return (xyw_byte)((addr - XYW_DEVICE_AREA_START) & 0x0F); } //// Read/write memory helpers@@ -159,8 +107,6 @@ writeb(addr, (xyw_byte)(val >> 8));
writeb(addr + 1, (xyw_byte)(val & 0xFF)); } -//// Device dispatchers - //// System stack management (frame-based) // Each frame is SYSTEM_FRAME_SIZE (8) bytes: // offset 0-1: return address (xyw_word, big-endian)@@ -174,7 +120,7 @@ static void ss_push_frame(xyw_word return_addr)
{ if (s >= SYSTEM_MAX_FRAMES) { - xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_OVERFLOW; + xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_SYSTEM_STACK_OVERFLOW; return; } xyw_byte *base = &system_stack[s * SYSTEM_FRAME_SIZE];@@ -193,7 +139,7 @@ static xyw_word ss_pop_frame()
{ if (s < 1) { - xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_UNDERFLOW; + xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_SYSTEM_STACK_UNDERFLOW; return -1; } s--;@@ -208,29 +154,86 @@ for (int i = 0; i < SYSTEM_FRAME_SIZE; i++) base[i] = 0;
return return_addr; } +//// Internal VM state + +static int XYW_SYSTEM_ERROR_state() +{ + return xyw_memory[SYSTEM_STATE] & XYW_STATE_ERROR; +} + +static int system_running_state() +{ + return xyw_memory[SYSTEM_STATE] & XYW_STATE_RUNNING; +} + +static int system_waiting_state() +{ + return xyw_memory[SYSTEM_STATE] & XYW_STATE_WAITING; +} + +//// Privileged VM API (public wrappers) + +void xyw_vm_push_frame(xyw_word return_addr) { ss_push_frame(return_addr); } +xyw_word xyw_vm_pop_frame(void) { return ss_pop_frame(); } +xyw_byte xyw_vm_get_system_depth(void) { return s; } + +void xyw_vm_set_running(int running) +{ + if (running) + xyw_memory[SYSTEM_STATE] |= XYW_STATE_RUNNING; + else + xyw_memory[SYSTEM_STATE] &= ~XYW_STATE_RUNNING; +} + +void xyw_vm_set_waiting(int waiting) +{ + if (waiting) + xyw_memory[SYSTEM_STATE] |= XYW_STATE_WAITING; + else + xyw_memory[SYSTEM_STATE] &= ~XYW_STATE_WAITING; +} + +int xyw_vm_is_running(void) { return system_running_state(); } +int xyw_vm_is_waiting(void) { return system_waiting_state(); } +int xyw_vm_is_error_state(void) { return XYW_SYSTEM_ERROR_state(); } + +void xyw_vm_reset(void) +{ + memset(xyw_memory, 0, sizeof(xyw_memory)); + memset(user_stack, 0, sizeof(user_stack)); + memset(system_stack, 0, sizeof(system_stack)); + s = 0; + u = 0; + x_val = 0; + y_val = 0; + xw_storage = 0; + yw_storage = 0; + pc_storage = 0; + xyw_argv_processed = 0; + xyw_exec_args_active = 0; +} + //// User stack management static void us_push(xyw_byte val) { - if (u > USER_STACK_SIZE - 1) + if (u > XYW_USER_STACK_SIZE - 1) { - xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_OVERFLOW; + xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_USER_STACK_OVERFLOW; return; } - XYW_DBG(" => %02X\n", val); user_stack[u++] = val; } static void us_pushw(xyw_word val) { - if (u > USER_STACK_SIZE - 2) + if (u > XYW_USER_STACK_SIZE - 2) { - xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_OVERFLOW; + xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_USER_STACK_OVERFLOW; return; } user_stack[u] = (xyw_byte)(val >> 8); user_stack[u + 1] = (xyw_byte)(val & 0xFF); - XYW_DBG(" => %04X\n", val); u += 2; }@@ -238,7 +241,7 @@ static xyw_byte us_pop()
{ if (u < 1) { - xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_UNDERFLOW; + xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_USER_STACK_UNDERFLOW; return -1; } u -= 1;@@ -251,7 +254,7 @@ static xyw_word us_popw()
{ if (u < 2) { - xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_UNDERFLOW; + xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_USER_STACK_UNDERFLOW; return -1; } xyw_word val = (xyw_word)((user_stack[u - 2] << 8) | user_stack[u - 1]);@@ -341,387 +344,184 @@ #define TOP (w ? (xyw_word)((user_stack[u - 2] << 8) | user_stack[u - 1]) : (xyw_word)user_stack[u - 1])
//// Initialize devices +#define DEV_DATA(n) (&xyw_memory[XYW_DEVICE_AREA_START + (n) * 16]) + xyw_device xyw_devices[XYW_TOTAL_DEVICES] = { // System device - {&xyw_memory[0xff00], system_setup, system_input, system_output, 0}, + {DEV_DATA(0), system_setup, system_input, system_output, 0, 0, system_save, system_restore, system_tick}, // Terminal device - {&xyw_memory[0xff10], terminal_init, terminal_input, terminal_output, terminal_teardown}, + {DEV_DATA(1), terminal_setup, terminal_input, terminal_output, terminal_teardown, terminal_poll, terminal_save, terminal_restore, 0}, // Clock device - {&xyw_memory[0xff20], clock_setup, clock_input, clock_output, 0}, + {DEV_DATA(2), clock_setup, clock_input, clock_output, 0, clock_poll, 0, 0, 0}, // File device - {&xyw_memory[0xff30], file_setup, file_input, file_output, 0}, + {DEV_DATA(3), file_setup, file_input, file_output, 0, 0, 0, 0, 0}, // Beeper device - {&xyw_memory[0xff40], 0, beeper_input, beeper_output, beeper_teardown}, + {DEV_DATA(4), 0, beeper_input, beeper_output, beeper_teardown, 0, 0, 0, 0}, // ... }; // clang-format on -static int system_error_state() +static int has_poll_devices(void) { - return xyw_memory[SYSTEM_STATE] & SYSTEM_STATE_ERROR; + for (int i = 0; i < XYW_TOTAL_DEVICES; i++) + if (xyw_devices[i].poll) + return 1; + return 0; } -static int system_running_state() -{ - return xyw_memory[SYSTEM_STATE] & SYSTEM_STATE_RUNNING; -} +//// Privileged snapshot/load API -static int system_waiting_state() +void xyw_vm_save_snapshot(xyw_vm_snapshot *snap) { - return xyw_memory[SYSTEM_STATE] & SYSTEM_STATE_WAITING; + XYW_DBG("== Saving snapshot\n"); + memcpy(snap->memory, xyw_memory, sizeof(xyw_memory)); + memcpy(snap->user_stack, user_stack, sizeof(user_stack)); + memcpy(snap->system_stack, system_stack, sizeof(system_stack)); + snap->s = s; + snap->u = u; + snap->x_val = x_val; + snap->y_val = y_val; + snap->pc_val = *pc; + snap->xw_val = *xw; + snap->yw_val = *yw; + snap->argv_processed = xyw_argv_processed; + memcpy(snap->exec_args, xyw_exec_args, sizeof(xyw_exec_args)); + snap->exec_args_active = xyw_exec_args_active; + for (int i = 0; i < XYW_TOTAL_DEVICES; i++) + if (xyw_devices[i].save) + xyw_devices[i].save(xyw_devices[i].data, snap->device_state[i]); } -//// Internal VM state -xyw_byte error_handler_entry_s = 0; - -// Argument feeding state (CLI args) -static int arg_index = 1; -static int arg_char_pos = 0; - -// Sub-image argument feeding state -#define MAX_SUBIMAGE_ARGS 256 -static char subimage_args_buf[MAX_SUBIMAGE_ARGS]; -static int subimage_args_active = 0; -static int subimage_args_pos = 0; - -static int process_events() +void xyw_vm_restore_snapshot(xyw_vm_snapshot *snap) { - // Error handling - if (xyw_memory[SYSTEM_ERROR] != XYW_ERROR_NONE && !system_error_state()) + XYW_DBG("== Restoring snapshot\n"); + memcpy(xyw_memory, snap->memory, sizeof(xyw_memory)); + memcpy(user_stack, snap->user_stack, sizeof(user_stack)); + memcpy(system_stack, snap->system_stack, sizeof(system_stack)); + s = snap->s; + u = snap->u; + x_val = snap->x_val; + y_val = snap->y_val; + *pc = snap->pc_val; + *xw = snap->xw_val; + *yw = snap->yw_val; + xyw_argv_processed = snap->argv_processed; + memcpy(xyw_exec_args, snap->exec_args, sizeof(xyw_exec_args)); + xyw_exec_args_active = snap->exec_args_active; + for (int i = 0; i < XYW_TOTAL_DEVICES; i++) { - xyw_word on_error_handler = XYW_PEEKW(&xyw_memory[SYSTEM_ON_ERROR]); - // If an error handler is set, jump to it - if (on_error_handler != 0) + if (xyw_devices[i].restore) { - XYW_DBG("Error occurred: %02X, jumping to error handler at %04X\n", xyw_memory[SYSTEM_ERROR], on_error_handler); - // Enter error state - xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_ERROR; - error_handler_entry_s = s; // Record stack depth before pushing return address - ss_push_frame(*pc); - *pc = on_error_handler; + xyw_devices[i].restore(xyw_devices[i].data, snap->device_state[i]); } - else + else if (xyw_devices[i].setup) { - // No error handler: halt execution - xyw_memory[SYSTEM_STATE] &= ~SYSTEM_STATE_RUNNING; - return xyw_memory[SYSTEM_ERROR]; + xyw_devices[i].setup(xyw_devices[i].data, dev_error); } } - return 0; } -//// Snapshot management - -typedef struct { - xyw_byte memory[MEMORY_SIZE]; - xyw_byte user_stack[USER_STACK_SIZE]; - xyw_byte system_stack[SYSTEM_MAX_FRAMES * SYSTEM_FRAME_SIZE]; - xyw_byte s, u, x_val, y_val; - xyw_word pc_val, xw_val, yw_val; - xyw_byte error_handler_entry_s; - int argv_processed; - int arg_index; - int arg_char_pos; - char subimage_args_buf[MAX_SUBIMAGE_ARGS]; - int subimage_args_active; - int subimage_args_pos; -} xyw_image_snapshot; - -static xyw_image_snapshot exec_stack[MAX_IMAGE_EXEC_DEPTH]; -static int exec_stack_depth = 0; -static int exec_pending = 0; -static char exec_path_buf[MAX_IMAGE_EXEC_PATH_LENGTH]; - -// Read a null-terminated string out of VM memory into a local buffer -static void get_string_from_memory(xyw_word addr, char *buf, size_t bufsize) +int xyw_vm_load_image(const char *path, const char *args) { - size_t i; - for (i = 0; i < bufsize - 1 && xyw_memory[addr + i] != 0; i++) - buf[i] = xyw_memory[addr + i]; - buf[i] = '\0'; -} - -void xyw_request_exec(xyw_word path_addr) -{ - get_string_from_memory(path_addr, exec_path_buf, sizeof(exec_path_buf)); - exec_pending = 1; -} - -static void split_exec_string(char *full, char **path, char **argstart) -{ - char *sp = strchr(full, ' '); - if (sp) + FILE *fp = fopen(path, "rb"); + if (!fp) { - *sp = '\0'; - *argstart = sp + 1; + fprintf(stderr, "Error - Could not open file [%s]\n", path); + xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_IMAGE_NOT_FOUND; + xyw_memory[SYSTEM_STATE] &= ~XYW_STATE_RUNNING; + return -1; } - else + fseek(fp, 0, SEEK_END); + long file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + if (file_size < 0) { - *argstart = NULL; + fprintf(stderr, "Error - Could not determine file size [%s]\n", path); + fclose(fp); + xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_IMAGE_READ_ERROR; + xyw_memory[SYSTEM_STATE] &= ~XYW_STATE_RUNNING; + return -1; } - *path = full; -} -static void reset_vm_state(void) -{ - memset(xyw_memory, 0, sizeof(xyw_memory)); - memset(user_stack, 0, sizeof(user_stack)); - memset(system_stack, 0, sizeof(system_stack)); - s = 0; - u = 0; - x_val = 0; - y_val = 0; - xw_storage = 0; - yw_storage = 0; - pc_storage = 0; - error_handler_entry_s = 0; - xyw_argv_processed = 0; -} - -static int load_and_launch(const char *path, char *argstart) -{ - FILE *fp = fopen(path, "rb"); - if (!fp) + if ((size_t)file_size > XYW_MEMORY_SIZE) { - fprintf(stderr, "Error - Could not open file [%s]\n", path); - xyw_memory[SYSTEM_ERROR] = XYW_ERROR_IMAGE_NOT_FOUND; - xyw_memory[SYSTEM_STATE] &= ~SYSTEM_STATE_RUNNING; + fprintf(stderr, "Error - Image exceeds %d-byte memory size [%s]\n", XYW_MEMORY_SIZE, path); + fclose(fp); + xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_IMAGE_TOO_LARGE; + xyw_memory[SYSTEM_STATE] &= ~XYW_STATE_RUNNING; return -1; } - size_t bytes_read = fread(&xyw_memory[USER_MEMORY_START], 1, MEMORY_SIZE - USER_MEMORY_START, fp); + size_t bytes_read = fread(&xyw_memory[0x000], 1, XYW_MEMORY_SIZE - 0x000, fp); fclose(fp); if (bytes_read == 0) { fprintf(stderr, "Error - Could not read file contents [%s]\n", path); - xyw_memory[SYSTEM_ERROR] = XYW_ERROR_IMAGE_READ_ERROR; - xyw_memory[SYSTEM_STATE] &= ~SYSTEM_STATE_RUNNING; + xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_IMAGE_READ_ERROR; + xyw_memory[SYSTEM_STATE] &= ~XYW_STATE_RUNNING; return -1; } - xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_RUNNING; + xyw_memory[SYSTEM_STATE] |= XYW_STATE_RUNNING; for (int i = 0; i < XYW_TOTAL_DEVICES; i++) + { if (xyw_devices[i].setup) - xyw_devices[i].setup(xyw_devices[i].data); - + { + xyw_devices[i].setup(xyw_devices[i].data, dev_error); + } + } + xyw_memory[SYSTEM_PAGE] = 0xff; *pc = 0x0000; - // Store sub-image arguments for deferred feeding via feed_next_event - // Include image path as first argument (matching CLI argv convention) - if (argstart) + if (args) { - snprintf(subimage_args_buf, MAX_SUBIMAGE_ARGS, "%s %s", path, argstart); - subimage_args_active = 1; - subimage_args_pos = 0; + size_t plen = strlen(path); + size_t remaining = XYW_EXEC_ARGS_SIZE - plen - 1; + memcpy(xyw_exec_args, path, plen); + xyw_exec_args[plen] = ' '; + strncpy(&xyw_exec_args[plen + 1], args, remaining - 1); + xyw_exec_args[XYW_EXEC_ARGS_SIZE - 1] = '\0'; + xyw_exec_args_active = 1; } else { - subimage_args_active = 0; + xyw_exec_args_active = 0; } return 0; } -static void save_snapshot(xyw_image_snapshot *snap) +// Reconstruct the modes given an opcode (used for debugging) +static const char *mode_suffix(xyw_byte opcode) { - XYW_DBG("== Saving snapshot"); - memcpy(snap->memory, xyw_memory, sizeof(xyw_memory)); - memcpy(snap->user_stack, user_stack, sizeof(user_stack)); - memcpy(snap->system_stack, system_stack, sizeof(system_stack)); - snap->s = s; - snap->u = u; - snap->x_val = x_val; - snap->y_val = y_val; - snap->pc_val = *pc; - snap->xw_val = *xw; - snap->yw_val = *yw; - snap->error_handler_entry_s = error_handler_entry_s; - snap->argv_processed = xyw_argv_processed; - snap->arg_index = arg_index; - snap->arg_char_pos = arg_char_pos; - memcpy(snap->subimage_args_buf, subimage_args_buf, sizeof(subimage_args_buf)); - snap->subimage_args_active = subimage_args_active; - snap->subimage_args_pos = subimage_args_pos; + static char buf[4]; + int i = 0; + if (opcode & XYW_MODE_X) buf[i++] = 'x'; + if (opcode & XYW_MODE_Y) buf[i++] = 'y'; + if (opcode & XYW_MODE_W) buf[i++] = 'w'; + buf[i] = '\0'; + return buf; } -static void restore_snapshot(xyw_image_snapshot *snap) +// Format the first four bytes on the stack (used for debugging) +static const char *format_stack_peek(xyw_byte mode_w) { - XYW_DBG("== Restoring snapshot"); - memcpy(xyw_memory, snap->memory, sizeof(xyw_memory)); - memcpy(user_stack, snap->user_stack, sizeof(user_stack)); - memcpy(system_stack, snap->system_stack, sizeof(system_stack)); - s = snap->s; - u = snap->u; - x_val = snap->x_val; - y_val = snap->y_val; - *pc = snap->pc_val; - *xw = snap->xw_val; - *yw = snap->yw_val; - error_handler_entry_s = snap->error_handler_entry_s; - xyw_argv_processed = snap->argv_processed; - arg_index = snap->arg_index; - arg_char_pos = snap->arg_char_pos; - memcpy(subimage_args_buf, snap->subimage_args_buf, sizeof(subimage_args_buf)); - subimage_args_active = snap->subimage_args_active; - subimage_args_pos = snap->subimage_args_pos; - - // Devices with state outside xyw_memory (file cursor, clock timer target, - // beeper device) don't roll back automatically — re-sync to a clean slate - for (int i = 0; i < XYW_TOTAL_DEVICES; i++) - if (xyw_devices[i].setup) - xyw_devices[i].setup(xyw_devices[i].data); -} - -static void handle_exec_requests(void) -{ - if (!exec_pending) - return; - exec_pending = 0; - - if (exec_stack_depth >= MAX_IMAGE_EXEC_DEPTH) + static char buf[24]; + if (mode_w) { - xyw_memory[SYSTEM_ERROR] = XYW_ERROR_EXEC_DEPTH_EXCEEDED; - return; + xyw_word top = u >= 2 ? (xyw_word)((user_stack[u-2] << 8) | user_stack[u-1]) : 0; + xyw_word second = u >= 4 ? (xyw_word)((user_stack[u-4] << 8) | user_stack[u-3]) : 0; + snprintf(buf, sizeof(buf), "[ %04X %04X ]", second, top); } - save_snapshot(&exec_stack[exec_stack_depth++]); - - char path_copy[MAX_IMAGE_EXEC_PATH_LENGTH]; - strncpy(path_copy, exec_path_buf, sizeof(path_copy) - 1); - path_copy[sizeof(path_copy) - 1] = '\0'; - char *path, *argstart; - split_exec_string(path_copy, &path, &argstart); - - reset_vm_state(); - if (load_and_launch(path, argstart) != 0) + else { - // Restore parent state so error handler can run in the caller's context - restore_snapshot(&exec_stack[--exec_stack_depth]); + xyw_byte b3 = u >= 4 ? user_stack[u-4] : 0; + xyw_byte b2 = u >= 3 ? user_stack[u-3] : 0; + xyw_byte b1 = u >= 2 ? user_stack[u-2] : 0; + xyw_byte b0 = u >= 1 ? user_stack[u-1] : 0; + snprintf(buf, sizeof(buf), "[%02X %02X %02X %02X]", b3, b2, b1, b0); } -} - -//// Event dispatch (WAITING state) - -// Addresses of all event handler slots — add new handlers here. -// Both has_event_handlers() and feed_next_event() stay in sync via this table. -static const xyw_word event_handler_addrs[] = { - TERMINAL_ON_ARGUMENT, - TERMINAL_ON_KEYPRESS, - CLOCK_ON_TIMER_ELAPSED, -}; -#define NUM_EVENT_HANDLERS (sizeof(event_handler_addrs) / sizeof(event_handler_addrs[0])) - -static int has_event_handlers(void) -{ - for (size_t i = 0; i < NUM_EVENT_HANDLERS; i++) - if (XYW_PEEKW(&xyw_memory[event_handler_addrs[i]])) - return 1; - return 0; -} - -// Feed the next external event and dispatch its handler. -// Returns 1 if an event was dispatched, 0 if no more events. -static int feed_next_event(void) -{ - // Feed command line arguments - xyw_word on_arg_handler = XYW_PEEKW(&xyw_memory[TERMINAL_ON_ARGUMENT]); - if (!xyw_argv_processed && on_arg_handler) - { - if (subimage_args_active) - { - // Feed from sub-image argument string (space-separated) - char c = subimage_args_buf[subimage_args_pos]; - if (c == ' ') - { - xyw_memory[TERMINAL_INPUT] = XYW_ARGUMENT_SEPARATOR; - subimage_args_pos++; - } - else if (c != '\0') - { - xyw_memory[TERMINAL_INPUT] = (xyw_byte)c; - subimage_args_pos++; - } - else - { - xyw_memory[TERMINAL_INPUT] = XYW_END_OF_ARGUMENTS; - xyw_argv_processed = 1; - subimage_args_active = 0; - } - } - else if (arg_index < xyw_argc) - { - // Feed from CLI argv - char c = xyw_argv[arg_index][arg_char_pos]; - if (c != '\0') - { - xyw_memory[TERMINAL_INPUT] = (xyw_byte)c; - arg_char_pos++; - } - else if (arg_index < xyw_argc - 1) - { - xyw_memory[TERMINAL_INPUT] = XYW_ARGUMENT_SEPARATOR; - arg_index++; - arg_char_pos = 0; - } - else - { - xyw_memory[TERMINAL_INPUT] = XYW_END_OF_ARGUMENTS; - xyw_argv_processed = 1; - } - } - else - { - // No arguments, just send end marker - xyw_memory[TERMINAL_INPUT] = XYW_END_OF_ARGUMENTS; - xyw_argv_processed = 1; - } - xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_RUNNING; - // Sentinel return: handler's RTS returns to device space, cleanly ending dispatch - ss_push_frame(DEVICE_AREA_START); - *pc = on_arg_handler; - return 1; - } - - // Keypress handling (non-blocking: returns 0 on timeout) - xyw_word on_keypress_handler = XYW_PEEKW(&xyw_memory[TERMINAL_ON_KEYPRESS]); - - // Timer handling - xyw_word on_timer_handler = XYW_PEEKW(&xyw_memory[CLOCK_ON_TIMER_ELAPSED]); - - // Polling loop: keep checking until an event fires or exit is requested - while (on_keypress_handler || on_timer_handler) - { - if (on_keypress_handler) - { - int c = getch_timeout(POLL_INTERVAL_MS); - if (c > 0) - { - if (c == 3 || c == 4) return 0; // CTRL+C or CTRL+D - xyw_memory[TERMINAL_INPUT] = (xyw_byte)c; - xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_RUNNING; - // Sentinel return: handler's RTS returns to device space, cleanly ending dispatch - ss_push_frame(DEVICE_AREA_START); - *pc = on_keypress_handler; - return 1; - } - // c == 0: no key this cycle — fall through to timer check - } - - if (on_timer_handler) - { - int elapsed = on_keypress_handler - ? (readw(CLOCK_TIMER) == 0 && readb(CLOCK_ON_TIMER_ELAPSED)) - : readb(CLOCK_ON_TIMER_ELAPSED); // blocking: timer-only program - if (elapsed) - { - xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_RUNNING; - // Sentinel return: handler's RTS returns to device space, cleanly ending dispatch - ss_push_frame(DEVICE_AREA_START); - *pc = on_timer_handler; - return 1; - } - } - } - - return 0; + return buf; } //// Main evaluation function - runs from current pc until BRK@@ -737,47 +537,67 @@ xyw_word saved_yw = *yw;
if (xyw_debug) { - xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_DEBUG; + xyw_memory[SYSTEM_STATE] |= XYW_STATE_DEBUG; } while (1) { - if (*pc >= DEVICE_AREA_START) + if (*pc >= XYW_DEVICE_AREA_START) { - xyw_memory[SYSTEM_STATE] &= ~SYSTEM_STATE_RUNNING; + xyw_memory[SYSTEM_STATE] &= ~XYW_STATE_RUNNING; } if (!system_running_state()) { if (system_waiting_state()) { - if (feed_next_event()) + // Poll devices for events + // poll returns: 1=dispatched, 0=no active handler, -1=active but no event yet + int dispatched = 0; + while (system_waiting_state()) + { + int active = 0; + for (int i = 0; i < XYW_TOTAL_DEVICES; i++) + { + if (!xyw_devices[i].poll) continue; + int r = xyw_devices[i].poll(xyw_devices[i].data); + if (r > 0) { dispatched = 1; break; } + if (r < 0) active = 1; + } + if (dispatched) break; + if (!active) break; + } + if (dispatched) { continue; // handler was dispatched, go execute it } - xyw_memory[SYSTEM_STATE] &= ~SYSTEM_STATE_WAITING; + xyw_memory[SYSTEM_STATE] &= ~XYW_STATE_WAITING; } - if (exec_stack_depth > 0) + if (xyw_system_try_restore_parent()) { - restore_snapshot(&exec_stack[--exec_stack_depth]); continue; // resume the parent exactly where it left off } break; // no parent -- genuinely done } - process_events(); - handle_exec_requests(); + // Tick devices (error handling, exec requests, etc.) + for (int i = 0; i < XYW_TOTAL_DEVICES; i++) + if (xyw_devices[i].tick) + xyw_devices[i].tick(xyw_devices[i].data); if (!system_running_state()) { continue; } - XYW_DBG(" -- PC: %04X -> %02X (%s) [U:%02X|S:%02X|X:%02X|Y:%02X|XW:%04X|YW:%04X]\n", *pc, xyw_memory[*pc], xyw_instructions[xyw_memory[*pc] & 0x1F], u, s, *x, *y, *xw, *yw); + XYW_DBG(" -- pc:%04X %s%-3s x:%02X y:%02X xw:%04X yw:%04X stack:%s\n", + *pc, xyw_instructions[xyw_memory[*pc] & 0x1F], mode_suffix(xyw_memory[*pc]), + *x, *y, *xw, *yw, + format_stack_peek(xyw_memory[*pc] & XYW_MODE_W)); switch (xyw_memory[*pc]) { // clang-format off /* HLT */ case 0x00: - xyw_memory[SYSTEM_STATE] &= ~SYSTEM_STATE_RUNNING; - if (has_event_handlers()) + xyw_memory[SYSTEM_STATE] &= ~XYW_STATE_RUNNING; + if (has_poll_devices()) { - xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_WAITING; + xyw_memory[SYSTEM_STATE] |= XYW_STATE_WAITING; } break; /* NOP */ case 0x01: break;@@ -794,29 +614,29 @@ /* STB */ OPC_SR1r(0x06, xyw_byte val = us_pop(); writeb( ADDR(ARGX), val ); writeb( ADDR(ARGY), val ); );
/* STW */ OPC_SR1(0x07, xyw_word addr = ADDR(ARG); xyw_word val = us_popw(), writew( addr, val )); /* STW */ OPC_SR1r(0x07, xyw_word val = us_popw(); writew( ADDR(ARGX), val ); writew( ADDR(ARGY), val ); ); /* INC */ OPC_SR1(0x08, , SET(ARG + 1)); - /* INC */ OPC_SR1r(0x08, SET(ARGX + 1); SET(ARGY + 1); ); + /* INC */ OPC_SR1r(0x08, SETX(ARGX + 1); SETY(ARGY + 1); ); /* DEC */ OPC_SR1(0x09, , SET(ARG - 1)); - /* DEC */ OPC_SR1r(0x09, SET(ARGX - 1); SET(ARGY - 1); ); + /* DEC */ OPC_SR1r(0x09, SETX(ARGX - 1); SETY(ARGY - 1); ); /* SHL */ OPC_SR1(0x0A, xyw_byte val = us_pop(), SET(ARG << val)); - /* SHL */ OPC_SR1r(0x0A, xyw_byte val = us_pop(); SET(ARGX << val); SET(ARGY << val); ); + /* SHL */ OPC_SR1r(0x0A, xyw_byte val = us_pop(); SETX(ARGX << val); SETY(ARGY << val); ); /* SHR */ OPC_SR1(0x0B, xyw_byte val = us_pop(), SET(ARG >> val)); - /* SHR */ OPC_SR1r(0x0B, xyw_byte val = us_pop(); SET(ARGX >> val); SET(ARGY >> val); ); + /* SHR */ OPC_SR1r(0x0B, xyw_byte val = us_pop(); SETX(ARGX >> val); SETY(ARGY >> val); ); /* ADD */ OPC_SR2(0x0C, PUSH(a + b)); /* SUB */ OPC_SR2(0x0D, PUSH(a - b)); /* MUL */ OPC_SR2(0x0E, PUSH(a * b)); - /* DIV */ OPC_SR2(0x0F, if (b) { PUSH(a % b); PUSH(a / b); } else { xyw_memory[SYSTEM_ERROR] = XYW_ERROR_DIVISION_BY_ZERO; } ); + /* DIV */ OPC_SR2(0x0F, if (b) { PUSH(a % b); PUSH(a / b); } else { xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_DIVISION_BY_ZERO; } ); /* EQU */ OPC_SR2(0x10, us_push(a == b)); /* NEQ */ OPC_SR2(0x11, us_push(a != b)); /* GTH */ OPC_SR2(0x12, us_push(a > b)); /* LTH */ OPC_SR2(0x13, us_push(a < b)); /* NOT */ OPC_SR1(0x14, , SET(~ARG)); - /* NOT */ OPC_SR1r(0x14, SET(~ARGX); SET(~ARGY); ); + /* NOT */ OPC_SR1r(0x14, SETX(~ARGX); SETY(~ARGY); ); /* AND */ OPC_SR2(0x15, PUSH(a & b)); /* IOR */ OPC_SR2(0x16, PUSH(a | b)); /* XOR */ OPC_SR2(0x17, PUSH(a ^ b)); /* DUP */ OPC_SR1(0x18, , SET(TOP)); /* DUP */ OPC_SR1r(0x18, SETX(TOP); SETY(TOP); ); - /* SWP */ OPC_SR2(0x19, if ((rx||ry)) { SET(b); } else { PUSH(b); PUSH(a); } ); + /* SWP */ OPC_SR2(0x19, if (rx && ry) { SETX(b); SETY(a); } else if (rx) { PUSH(b); SETX(a); } else if (ry) { PUSH(b); SETY(a); } else { PUSH(b); PUSH(a); } ); /* OVR */ OPC_OVR(0x1A); /* ROT */ OPC_ROT(0x1B); /* JMP */ OPC_SR1(0x1C, , *pc = ADDR(ARG)-1;);@@ -824,16 +644,14 @@ /* JCN */ OPC_SR1(0x1D, , xyw_word addr = ADDR(ARG); xyw_byte cond = us_pop(); if (cond) { *pc = addr-1; });
/* JSR */ OPC_SR1(0x1E, xyw_word addr = ADDR(ARG), ss_push_frame(*pc+1); *pc = addr-1;); /* RTS */ case 0x1F: *pc = ss_pop_frame()-1; - // If returning from error handler, clear flag to allow re-trigger - if ((system_error_state()) && s == error_handler_entry_s) { - xyw_memory[SYSTEM_STATE] &= ~SYSTEM_STATE_ERROR; - error_handler_entry_s = 0; - } + break; + default: + xyw_memory[SYSTEM_ERROR] = XYW_SYSTEM_ERROR_INVALID_OPCODE; break; // clang-format on } (*pc)++; - if (xyw_memory[SYSTEM_ERROR] != XYW_ERROR_NONE) continue; + if (xyw_memory[SYSTEM_ERROR] != XYW_SYSTEM_ERROR_NONE) continue; } // Restore register state saved at entry (protects caller from event handler side-effects) *x = saved_x;@@ -843,6 +661,12 @@ *yw = saved_yw;
return xyw_memory[SYSTEM_ERROR]; } +static void xyw_signal_exit(int sig) +{ + (void)sig; + exit(1); +} + static void xyw_teardown_devices(void) { for (int i = 0; i < XYW_TOTAL_DEVICES; i++)@@ -858,8 +682,10 @@ //// Main run function
int xyw_run(const char *input) { atexit(xyw_teardown_devices); + signal(SIGINT, xyw_signal_exit); + signal(SIGTERM, xyw_signal_exit); - if (load_and_launch(input, NULL) != 0) + if (xyw_vm_load_image(input, NULL) != 0) { return -1; }