From e8d0cd5e96621b32c06d9b34e4da8ad87da70c07 Mon Sep 17 00:00:00 2001 From: zongor Date: Sat, 14 Jun 2025 14:38:04 -0400 Subject: [PATCH] add Q16.16 real, uint32, and int32 opcodes --- src/Makefile | 4 +- src/debug.c | 2 +- src/debug.h | 2 +- src/main.c | 20 ++-- src/vm.c | 284 ++++++++++++++++++++++++++++++++++----------------- src/vm.h | 41 ++++++-- 6 files changed, 241 insertions(+), 112 deletions(-) diff --git a/src/Makefile b/src/Makefile index 3624ea0..73e6392 100644 --- a/src/Makefile +++ b/src/Makefile @@ -2,13 +2,13 @@ # ----------------------- # Native build (gcc) CC_NATIVE = gcc -CFLAGS_NATIVE = -std=c89 -Wall -Wextra -Werror -Wno-unused-parameter +CFLAGS_NATIVE = -g -std=c89 -Wall -Wextra -Werror -Wno-unused-parameter LDFLAGS_NATIVE = LDLIBS_NATIVE = # WASM build (emscripten) CC_WASM = emcc -CFLAGS_WASM = -std=c89 -Wall -Wextra -Werror -Wno-unused-parameter #-s WASM=1 +CFLAGS_WASM = -g -std=c89 -Wall -Wextra -Werror -Wno-unused-parameter #-s WASM=1 LDFLAGS_WASM = #-s WASM=1 LDLIBS_WASM = diff --git a/src/debug.c b/src/debug.c index 0a0c2cf..0c6010c 100644 --- a/src/debug.c +++ b/src/debug.c @@ -1,6 +1,6 @@ #include "debug.h" -int core_dump(Data *memory, uint32_t memory_size) { +int core_dump(Word *memory, uint32_t memory_size) { FILE *file = fopen("memory_dump.bin", "wb"); if (!file) { perror("Failed to open file"); diff --git a/src/debug.h b/src/debug.h index 9cfc993..d33a20c 100644 --- a/src/debug.h +++ b/src/debug.h @@ -3,6 +3,6 @@ #include "vm.h" -int core_dump(Data *memory, uint32_t memory_size); +int core_dump(Word *memory, uint32_t memory_size); #endif diff --git a/src/main.c b/src/main.c index 4356b14..8e434d3 100644 --- a/src/main.c +++ b/src/main.c @@ -7,7 +7,7 @@ /* #define MEMORY_SIZE 65536 /\* 64KB memory (adjustable) *\/ */ #define MEMORY_SIZE 1024 -Data memory[MEMORY_SIZE] = {0}; /* Memory array */ +Word memory[MEMORY_SIZE] = {0}; /* Memory array */ uint32_t pc = 1; /* Program counter */ void mainloop() { @@ -33,15 +33,23 @@ int main() { memory[i++].u = 102; memory[i++].u = 103; memory[i++].u = 103; - memory[i++].u = OP_SUB; + memory[i++].u = OP_SUB_UINT; memory[i++].u = 100; memory[i++].u = 101; memory[i++].u = 100; - memory[i++].u = OP_JGT_INT; + memory[i++].u = OP_JGT_UINT; memory[i++].u = 100; memory[i++].u = 99; memory[i++].u = 1; - memory[i++].u = OP_REAL_TO_INT; + memory[i++].u = OP_REAL_TO_STRING; + memory[i++].u = 103; + memory[i++].u = 1; + memory[i++].u = 200; + memory[i++].u = OP_PRINT_STRING; + memory[i++].u = 201; + memory[i++].u = 1; + memory[i++].u = 1; + memory[i++].u = OP_REAL_TO_UINT; memory[i++].u = 103; memory[i++].u = 1; memory[i++].u = 103; @@ -68,8 +76,8 @@ int main() { memory[99].u = 0; memory[100].u = 5; memory[101].u = 1; - memory[102].f = 5.f; - memory[103].f = 5.f; + memory[102].q = FLOAT_TO_Q16_16(5.0f); + memory[103].q = FLOAT_TO_Q16_16(5.0f); #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(mainloop, 0, 1); diff --git a/src/vm.c b/src/vm.c index 381c762..960a370 100644 --- a/src/vm.c +++ b/src/vm.c @@ -1,11 +1,23 @@ #include "vm.h" -#include -#include + +#define MAX_LEN_INT32 11 +#define MAX_INT32 2147483647 +#define MIN_INT32 -2147483648 +const char radix_set[11] = "0123456789"; + +#define COMPARE_AND_JUMP(type, accessor, op) \ + do { \ + type value = memory[src1_addr].accessor; \ + type value2 = memory[src2_addr].accessor; \ + uint32_t jump_target = dest_addr; \ + pc = (value op value2) ? jump_target : pc; \ + return pc; \ + } while (0) /** * String copy in data memory. */ -void mem_strcpy(Data *memory, const char *str, uint32_t length, +void mem_strcpy(Word *memory, const char *str, uint32_t length, uint32_t dest_addr) { memory[dest_addr].u = length; uint32_t buffer_addr = dest_addr + 1; @@ -18,7 +30,7 @@ void mem_strcpy(Data *memory, const char *str, uint32_t length, /** * Step to the next opcode in the vm. */ -uint32_t step_vm(Data *memory, uint32_t memory_size, uint32_t pc) { +uint32_t step_vm(Word *memory, uint32_t memory_size, uint32_t pc) { Opcode opcode = memory[pc].u; uint32_t src1_addr = memory[pc + 1].u; uint32_t src2_addr = memory[pc + 2].u; @@ -34,147 +46,233 @@ uint32_t step_vm(Data *memory, uint32_t memory_size, uint32_t pc) { switch (opcode) { case OP_HALT: return 0; - case OP_ADD: + case OP_ADD_INT: + memory[dest_addr].u = memory[src1_addr].i + memory[src2_addr].i; + return pc; + + case OP_SUB_INT: + memory[dest_addr].u = memory[src1_addr].i - memory[src2_addr].i; + return pc; + + case OP_MUL_INT: + memory[dest_addr].u = memory[src1_addr].i * memory[src2_addr].i; + return pc; + + case OP_DIV_INT: + memory[dest_addr].u = memory[src1_addr].i / memory[src2_addr].i; + return pc; + + case OP_ADD_UINT: memory[dest_addr].u = memory[src1_addr].u + memory[src2_addr].u; return pc; - case OP_SUB: + case OP_SUB_UINT: memory[dest_addr].u = memory[src1_addr].u - memory[src2_addr].u; return pc; - case OP_MUL: + case OP_MUL_UINT: memory[dest_addr].u = memory[src1_addr].u * memory[src2_addr].u; return pc; - case OP_DIV: + case OP_DIV_UINT: memory[dest_addr].u = memory[src1_addr].u / memory[src2_addr].u; return pc; case OP_ADD_REAL: - memory[dest_addr].f = memory[src1_addr].f + memory[src2_addr].f; + memory[dest_addr].q = memory[src1_addr].q + memory[src2_addr].q; return pc; case OP_SUB_REAL: - memory[dest_addr].f = memory[src1_addr].f - memory[src2_addr].f; + memory[dest_addr].q = memory[src1_addr].q - memory[src2_addr].q; return pc; - case OP_MUL_REAL: - memory[dest_addr].f = memory[src1_addr].f * memory[src2_addr].f; - return pc; + case OP_MUL_REAL: { + int32_t a = memory[src1_addr].q; + int32_t b = memory[src2_addr].q; + /* Extract integer and fractional parts */ + int32_t a_int = a & Q16_16_INT_MASK; + int32_t a_frac = a & Q16_16_FRACTION_MASK; + int32_t b_int = b & Q16_16_INT_MASK; + int32_t b_frac = b & Q16_16_FRACTION_MASK; - case OP_DIV_REAL: - if (memory[src2_addr].f == 0.0f) { + /* Compute terms with explicit casting to prevent overflow */ + int32_t term1 = a_int * b_int; /* Integer × Integer */ + int32_t term2 = a_int * b_frac; /* Integer × Fractional */ + int32_t term3 = a_frac * b_int; /* Fractional × Integer */ + int32_t term4 = a_frac * b_frac; /* Fractional × Fractional */ + + /* Scale terms back to Q16.16 (avoid shifting negative values) */ + int32_t scaled_term1 = term1; + int32_t scaled_term2 = (term2 >> 16); + int32_t scaled_term3 = (term3 >> 16); + int32_t scaled_term4 = (term4 >> 16); /* 16-bit shift for 1/65536 scaling */ + /* Combine scaled terms with overflow checks */ + int32_t result = scaled_term1 + scaled_term2 + scaled_term3 + scaled_term4; + memory[dest_addr].q = result; + return pc; + } + + case OP_DIV_REAL: { + int32_t a = memory[src1_addr].q; + int32_t b = memory[src2_addr].q; + if (b == 0) { printf("Division by zero error at address %d\n", pc - 4); return 0; } - memory[dest_addr].f = memory[src1_addr].f / memory[src2_addr].f; - return pc; - case OP_REAL_TO_INT: { - memory[dest_addr].u = (uint32_t)memory[src1_addr].f; + + /* Check for overflow */ + int32_t a_int = a >> 16; + int32_t b_int = b >> 16; + + /* If a_int / b_int would overflow, clamp */ + if (b_int == 0 || (a_int > 0 && b_int < 0 && a_int > (MAX_INT32 / b_int)) || + (a_int < 0 && b_int > 0 && a_int < (MIN_INT32 / b_int))) { + return (a < 0) ? MIN_INT32 : MAX_INT32; + } + + /* Scale numerator and divide */ + int32_t scaled_a = a << 16; + int32_t result = scaled_a / b; + + memory[dest_addr].q = result; + return pc; } - case OP_INT_TO_REAL: { - memory[dest_addr].f = (float)memory[src1_addr].u; + + case OP_REAL_TO_INT: + memory[dest_addr].i = (int32_t)(memory[src1_addr].q >> 16); return pc; - } + + case OP_INT_TO_REAL: + memory[dest_addr].q = (int32_t)(memory[src1_addr].i << 16); + return pc; + + case OP_REAL_TO_UINT: + memory[dest_addr].u = (int32_t)(memory[src1_addr].q >> 16); + return pc; + + case OP_UINT_TO_REAL: + memory[dest_addr].q = (uint32_t)(memory[src1_addr].u << 16); + return pc; + case OP_MOV: memory[dest_addr] = memory[src1_addr]; return pc; case OP_JMP: pc = src1_addr; /* Jump to address */ return pc; + case OP_JEQ_UINT: { + COMPARE_AND_JUMP(uint32_t, u, ==); + } + case OP_JGT_UINT: { + COMPARE_AND_JUMP(uint32_t, u, >); + } + case OP_JLT_UINT: { + COMPARE_AND_JUMP(uint32_t, u, <); + } + case OP_JLE_UINT: { + COMPARE_AND_JUMP(uint32_t, u, <=); + } + case OP_JGE_UINT: { + COMPARE_AND_JUMP(uint32_t, u, >=); + } case OP_JEQ_INT: { - uint32_t value = memory[src1_addr].u; - uint32_t value2 = memory[src2_addr].u; - uint32_t jump_target = dest_addr; - - pc = (value == value2) ? jump_target : pc; - return pc; + COMPARE_AND_JUMP(int32_t, i, ==); } case OP_JGT_INT: { - uint32_t value = memory[src1_addr].u; - uint32_t value2 = memory[src2_addr].u; - uint32_t jump_target = dest_addr; - - pc = (value > value2) ? jump_target : pc; - return pc; + COMPARE_AND_JUMP(int32_t, i, >); } case OP_JLT_INT: { - uint32_t value = memory[src1_addr].u; - uint32_t value2 = memory[src2_addr].u; - uint32_t jump_target = dest_addr; - - pc = (value < value2) ? jump_target : pc; - return pc; + COMPARE_AND_JUMP(int32_t, i, <); } case OP_JLE_INT: { - uint32_t value = memory[src1_addr].u; - uint32_t value2 = memory[src2_addr].u; - uint32_t jump_target = dest_addr; - - pc = (value <= value2) ? jump_target : pc; - return pc; + COMPARE_AND_JUMP(int32_t, i, <=); } case OP_JGE_INT: { - uint32_t value = memory[src1_addr].u; - uint32_t value2 = memory[src2_addr].u; - uint32_t jump_target = dest_addr; - - pc = (value >= value2) ? jump_target : pc; - return pc; + COMPARE_AND_JUMP(int32_t, i, >=); } case OP_JEQ_REAL: { - float value = memory[src1_addr].f; - float value2 = memory[src2_addr].f; - uint32_t jump_target = dest_addr; - - pc = (value == value2) ? jump_target : pc; - return pc; + COMPARE_AND_JUMP(int32_t, i, ==); } case OP_JGT_REAL: { - float value = memory[src1_addr].f; - float value2 = memory[src2_addr].f; - uint32_t jump_target = dest_addr; - - pc = (value > value2) ? jump_target : pc; - return pc; + COMPARE_AND_JUMP(int32_t, u, >); } case OP_JLT_REAL: { - float value = memory[src1_addr].f; - float value2 = memory[src2_addr].f; - uint32_t jump_target = dest_addr; - - pc = (value < value2) ? jump_target : pc; - return pc; + COMPARE_AND_JUMP(int32_t, u, <); } case OP_JGE_REAL: { - float value = memory[src1_addr].f; - float value2 = memory[src2_addr].f; - uint32_t jump_target = dest_addr; - - pc = (value >= value2) ? jump_target : pc; - return pc; + COMPARE_AND_JUMP(int32_t, u, >=); } case OP_JLE_REAL: { - float value = memory[src1_addr].f; - float value2 = memory[src2_addr].f; - uint32_t jump_target = dest_addr; - - pc = (value <= value2) ? jump_target : pc; - return pc; + COMPARE_AND_JUMP(int32_t, u, <=); } case OP_INT_TO_STRING: { - int32_t a = (int32_t)memory[src1_addr].u; - char buffer[32]; - sprintf(buffer, "%d", a); - mem_strcpy(memory, buffer, strlen(buffer), dest_addr); + int32_t v = memory[src1_addr].i; + char buffer[MAX_LEN_INT32]; + int64_t n = v; + bool neg = n < 0; + if (neg) + n = -n; + int i = MAX_LEN_INT32; + do { + buffer[--i] = radix_set[n % 10]; + n /= 10; + } while (n > 0); + if (neg) + buffer[--i] = '-'; + /* Ensure at least one digit is written for 0 */ + if (v == 0) + buffer[--i] = '0'; + /* Copy from buffer[i] to buffer + MAX_LEN_INT32 */ + mem_strcpy(memory, buffer + i, MAX_LEN_INT32 - i, dest_addr); return pc; } case OP_REAL_TO_STRING: { - float a = memory[src1_addr].f; - char buffer[32]; - sprintf(buffer, "%f", a); - mem_strcpy(memory, buffer, strlen(buffer), dest_addr); + int32_t q = memory[src1_addr].q; + char buffer[32]; /* Max 10 digits for integer part + 6 for fractional + sign + + '.' + null */ + int i = 0, j = 0; + + /* Handle negative numbers */ + if (q < 0) { + buffer[i++] = '-'; + q = -q; + } + + /* Extract integer part (top 16 bits) */ + uint32_t int_part = q >> 16; + /* Extract fractional part (bottom 16 bits) */ + uint32_t frac_part = q & 0xFFFF; + + /* Convert integer part to string (reverse order) */ + if (int_part == 0) { + buffer[i++] = radix_set[0]; + } else { + char tmp[16]; + int tmp_i = 0; + while (int_part > 0) { + tmp[tmp_i++] = radix_set[int_part % 10]; + int_part /= 10; + } + while (tmp_i > 0) { + buffer[i++] = tmp[--tmp_i]; + } + } + + /* Convert fractional part to 6 decimal digits */ + buffer[i++] = '.'; + for (j = 0; j < 6; j++) { + frac_part *= 10; + buffer[i++] = + radix_set[frac_part >> 16]; /* Get integer part of (frac_part * 10) */ + frac_part &= 0xFFFF; /* Keep fractional part for next digit */ + } + + /* Null-terminate */ + buffer[i] = '\0'; + + /* Copy to memory */ + mem_strcpy(memory, buffer, i, dest_addr); return pc; } case OP_READ_STRING: { @@ -184,7 +282,7 @@ uint32_t step_vm(Data *memory, uint32_t memory_size, uint32_t pc) { int ch = getchar(); if (ch == '\n' || ch == EOF) { memory[buffer_addr + (length / 4)].c[length % 4] = '\0'; - break; + break; } memory[buffer_addr + (length / 4)].c[length % 4] = ch; length++; @@ -199,7 +297,7 @@ uint32_t step_vm(Data *memory, uint32_t memory_size, uint32_t pc) { for (i = 0; i < length; i++) { uint8_t ch = memory[string_addr + (i / 4)].c[i % 4]; if (ch == '\0') - break; + break; putchar(ch); } putchar('\n'); @@ -221,7 +319,7 @@ uint32_t step_vm(Data *memory, uint32_t memory_size, uint32_t pc) { uint32_t char2 = memory[addr2 + i].u; if (char1 != char2) { equal = 0; - break; + break; } if ((char1 & 0xFF) == '\0' && (char2 & 0xFF) == '\0') return pc; diff --git a/src/vm.h b/src/vm.h index 2856a78..79b4e38 100644 --- a/src/vm.h +++ b/src/vm.h @@ -3,24 +3,46 @@ #include "common.h" +/* Constants for Q16.16 */ +#define Q16_16_SCALE (1 << 16) +#define Q16_16_SCALE_FLOAT 65536.0f +#define Q16_16_FRACTION_MASK 0x0000FFFF +#define Q16_16_INT_MASK 0xFFFF0000 + +/* Convert float to Q16.16 (with rounding) */ +#define FLOAT_TO_Q16_16(f) ((int32_t)((f) * Q16_16_SCALE_FLOAT + 0.5f)) +/* Convert Q16.16 to float */ +#define Q16_16_TO_FLOAT(q) ((float)(q) / Q16_16_SCALE_FLOAT) + typedef union { - float f; - uint32_t u; - char c[4]; -} Data; + int32_t i; /* Integers */ + int32_t q; /* Q16.16 (32-bit fixed-point) */ + uint32_t u; /* Unsigned integers */ + char c[4]; /* 4 Byte char array for string packing */ +} Word; typedef enum { OP_HALT, /* terminate execution */ - OP_ADD, /* dest = src1 + src2 */ - OP_SUB, /* dest = src1 - src2 */ - OP_MUL, /* dest = src1 * src2 */ - OP_DIV, /* dest = src1 / src2 */ + OP_ADD_INT, /* dest = src1 + src2 */ + OP_SUB_INT, /* dest = src1 - src2 */ + OP_MUL_INT, /* dest = src1 * src2 */ + OP_DIV_INT, /* dest = src1 / src2 */ OP_JEQ_INT, /* jump to address dest if src1 as int == src2 as int */ OP_JGT_INT, /* jump to address dest if src1 as int > src2 as int*/ OP_JLT_INT, /* jump to address dest if src1 as int < src2 as int */ OP_JLE_INT, /* jump to address dest if src1 as int <= src2 as int */ OP_JGE_INT, /* jump to address dest if src1 as int >= src2 as int*/ OP_INT_TO_REAL, /* dest = src1 as f32 */ + OP_ADD_UINT, /* dest = src1 + src2 */ + OP_SUB_UINT, /* dest = src1 - src2 */ + OP_MUL_UINT, /* dest = src1 * src2 */ + OP_DIV_UINT, /* dest = src1 / src2 */ + OP_JEQ_UINT, /* jump to address dest if src1 as int == src2 as uint */ + OP_JGT_UINT, /* jump to address dest if src1 as int > src2 as uint*/ + OP_JLT_UINT, /* jump to address dest if src1 as int < src2 as uint */ + OP_JLE_UINT, /* jump to address dest if src1 as int <= src2 as uint */ + OP_JGE_UINT, /* jump to address dest if src1 as int >= src2 as uint*/ + OP_UINT_TO_REAL, /* dest = src1 as f32 */ OP_ADD_REAL, /* dest = src1 + src2 */ OP_SUB_REAL, /* dest = src1 - src2 */ OP_MUL_REAL, /* dest = src1 * src2 */ @@ -31,6 +53,7 @@ typedef enum { OP_JLT_REAL, /* jump to address dest if src1 as real < src2 as real */ OP_JLE_REAL, /* jump to address dest if src1 as real <= src2 as real */ OP_REAL_TO_INT, /* dest = src1 as int */ + OP_REAL_TO_UINT, /* dest = src1 as int */ OP_MOV, /* dest = src1 */ OP_JMP, /* jump to address src1 unconditionally */ OP_INT_TO_STRING, /* dest = src1 as str */ @@ -40,6 +63,6 @@ typedef enum { OP_CMP_STRING, /* dest = src1 */ } Opcode; -uint32_t step_vm(Data *memory, uint32_t memory_size, uint32_t pc); +uint32_t step_vm(Word *memory, uint32_t memory_size, uint32_t pc); #endif