all repos — xyw @ 0871170c5656a4e8890a8779e8bb6b17b09896c2

A minimal virtual machine and assembler for terminals.

Now saving registers in system stack, so that they can be used locally within subroutines.
h3rald h3rald@h3rald.com
Fri, 27 Feb 2026 06:55:10 +0100
commit

0871170c5656a4e8890a8779e8bb6b17b09896c2

parent

dff71d1935009002ad772f0a3246c823507f2976

M examples/factorial16.xywexamples/factorial16.xyw

@@ -22,12 +22,10 @@ fact.loop JMPw

.label fact.end0 ; Edge case: factorial of 0 is 1 $0001 - CLRxyw RTS .label fact.end1 ; Return result PSHxw - CLRxyw RTS .include "lib/putdw.xyw"
M examples/factorial8.xywexamples/factorial8.xyw

@@ -20,12 +20,10 @@ fact.loop JMPw

.label fact.end0 ; Edge case: factorial of 0 is 1 $01 - CLRxy RTS .label fact.end1 ; Return result PSHx - CLRxy RTS .include "lib/putd.xyw"
M examples/lib/putd.xywexamples/lib/putd.xyw

@@ -12,10 +12,8 @@ ; Call PUTD recursively with quotient (on top, consumed by POPx)

putd JSRw ; Remainder is still on stack, print it $30 ADD PUTC - CLRx RTS .label putd.single ; Print single digit PSHx $30 ADD PUTC - CLRx RTS
M examples/lib/putdw.xywexamples/lib/putdw.xyw

@@ -13,10 +13,8 @@ ; Call PUTDw recursively with quotient (on top, consumed by POPxw)

putdw JSRw ; Remainder (16-bit) is still on stack, get low byte and print it SWP POP $30 ADD PUTC - CLRxw RTS .label putdw.single ; Print single digit (need low byte of XW since value is 0-9) PSHxw SWP POP $30 ADD PUTC - CLRxw RTS
M examples/lib/puts.xywexamples/lib/puts.xyw

@@ -7,5 +7,4 @@ POPxw

.label puts.loop LDBxw PUTC INCxw LDBxw puts.loop JCNw - CLRxw RTS
M examples/print-args.xywexamples/print-args.xyw

@@ -14,18 +14,15 @@ LDBx arg.end EQU print-args.end JCNw

LDBx arg.sep EQU print-args.next JCNw ; Print typed character LDBx PUTC - CLRx RTS ; Print argument separator (, ) .label print-args.next $2C PUTC SP - CLRx RTS ; Print new line .label print-args.end NL - CLRx RTS .label end
M examples/time.xywexamples/time.xyw

@@ -45,7 +45,6 @@ .label ZEROPADx

POPx PSHx $0A LTH put-zero JCNw .label put-digit PSHx putd JSRw - CLRx RTS .label put-zero $30 PUTC
M xywrun.cxywrun.c

@@ -9,12 +9,16 @@ #include "devices/file.h"

#include "devices/beeper.h" #define MEMORY_SIZE 0x10000 -#define STACK_SIZE 0xff + +#define USER_STACK_SIZE 128 +#define SYSTEM_STACK_SIZE 384 +#define SYSTEM_FRAME_SIZE 8 +#define SYSTEM_MAX_FRAMES 48 #define USER_MEMORY_START 0x000 #define SYSTEM_MEMORY_START 0xfd00 #define USER_STACK_START 0xfd00 -#define SYSTEM_STACK_START 0xfe00 +#define SYSTEM_STACK_START 0xfd80 #define DEVICE_AREA_START 0xff00 /* system device addresses */

@@ -145,37 +149,59 @@ }

//// Device dispatchers -//// System stack management +//// System stack management (frame-based) +// Each frame is SYSTEM_FRAME_SIZE (8) bytes: +// offset 0-1: return address (xyw_word, big-endian) +// offset 2: x register (xyw_byte) +// offset 3: y register (xyw_byte) +// offset 4-5: xw register (xyw_word, big-endian) +// offset 6-7: yw register (xyw_word, big-endian) +// *s is a frame index (0..SYSTEM_MAX_FRAMES-1), not a byte offset. -static xyw_word ss_popw() +static void ss_push_frame(xyw_word return_addr) { - if (*s < 2) + if (*s >= SYSTEM_MAX_FRAMES) { - xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_UNDERFLOW; - return -1; + xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_OVERFLOW; + return; } - xyw_word val = readw(SYSTEM_STACK_START + (*s) - 2); - writew(SYSTEM_STACK_START + (*s) - 2, 0); - (*s) -= 2; - return val; + xyw_word base = SYSTEM_STACK_START + (*s) * SYSTEM_FRAME_SIZE; + writew(base + 0, return_addr); + xyw_memory[base + 2] = *x; + xyw_memory[base + 3] = *y; + writew(base + 4, *xw); + writew(base + 6, *yw); + (*s)++; } -static void ss_pushw(xyw_word val) +static xyw_word ss_pop_frame() { - if (*s > STACK_SIZE - 2) + if (*s < 1) { - xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_OVERFLOW; - return; + xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_UNDERFLOW; + return -1; } - writew(SYSTEM_STACK_START + *s, val); - (*s) += 2; + (*s)--; + xyw_word base = SYSTEM_STACK_START + (*s) * SYSTEM_FRAME_SIZE; + xyw_word return_addr = readw(base + 0); + *x = xyw_memory[base + 2]; + *y = xyw_memory[base + 3]; + *xw = readw(base + 4); + *yw = readw(base + 6); + // Clear the frame + writew(base + 0, 0); + xyw_memory[base + 2] = 0; + xyw_memory[base + 3] = 0; + writew(base + 4, 0); + writew(base + 6, 0); + return return_addr; } //// User stack management static void us_push(xyw_byte val) { - if (*u > STACK_SIZE - 1) + if (*u > USER_STACK_SIZE - 1) { xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_OVERFLOW; return;

@@ -186,7 +212,7 @@ }

static void us_pushw(xyw_word val) { - if (*u > STACK_SIZE - 2) + if (*u > USER_STACK_SIZE - 2) { xyw_memory[SYSTEM_ERROR] = XYW_ERROR_STACK_OVERFLOW; return;

@@ -324,7 +350,7 @@ 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_pushw(*pc); + ss_push_frame(*pc); *pc = on_error_handler; } else

@@ -374,6 +400,12 @@ int xyw_eval(xyw_word start_pc)

{ *pc = start_pc; + // Save register state so event handlers don't corrupt the caller's registers + xyw_byte saved_x = *x; + xyw_byte saved_y = *y; + xyw_word saved_xw = *xw; + xyw_word saved_yw = *yw; + if (xyw_debug) { xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_DEBUG;

@@ -385,7 +417,7 @@ 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);

switch (xyw_memory[*pc]) { // clang-format off - /* CLR */ OPC_SR1(0x00, , if (rx||ry) { SET(0); } else { return 0; }); + /* CLR */ OPC_SR1(0x00, , if (rx||ry) { SET(0); } else { *x = saved_x; *y = saved_y; *xw = saved_xw; *yw = saved_yw; return 0; }); /* CLR */ OPC_SR1r(0x00, if (w) { *xw = 0; *yw = 0; } else { *x = 0; *y = 0; } ); /* SET */ OPC_SR1(0x01, , if (w) { SET(0xFFFF); } else { SET(0xFF); }); /* SET */ OPC_SR1r(0x01, if (w) { *xw = 0xFFFF; *yw = 0xFFFF; } else { *x = 0xFF; *y = 0xFF; } );

@@ -393,7 +425,6 @@ /* PSH */ OPC_SR1(0x02, , if (rx||ry) { PUSH(ARG); } else { (*pc)++; PUSH(READ(*pc)); if (w) { (*pc)++; } })

/* PSH */ OPC_SR1r(0x02, PUSH(a); PUSH(b); ) /* POP */ OPC_SR1(0x03, , if (rx||ry) { SET(POP()); } else { POP(); } ); /* POP */ OPC_SR1r(0x03, if (w) { xyw_word val = us_popw(); *xw = val; *yw = val; } else { xyw_byte val = us_pop(); *x = val; *y = val; } ); - /* LDB */ OPC_SR1(0x04, , us_push(readb(ADDR(ARG)))); /* LDB */ OPC_SR1r(0x04, us_push(readb(ADDR(ARGX))); us_push(readb(ADDR(ARGY))); ); /* LDW */ OPC_SR1(0x05, , us_pushw(readw(ADDR(ARG)) ));

@@ -430,9 +461,9 @@ /* OVR */ OPC_SR2(0x1A, PUSH(b); PUSH(a); PUSH(b));

/* ROT */ OPC_SR2(0x1B, PUSH(b); PUSH(POP()); PUSH(a)); /* JMP */ OPC_SR1(0x1C, , *pc = ADDR(ARG)-1;); /* 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_pushw(*pc+1); *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_popw()-1; + *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;

@@ -443,6 +474,11 @@ // clang-format on

} (*pc)++; } + // Restore register state saved at entry (protects caller from event handler side-effects) + *x = saved_x; + *y = saved_y; + *xw = saved_xw; + *yw = saved_yw; return 0; }