350 lines
9.4 KiB
C
350 lines
9.4 KiB
C
#include "vm.h"
|
|
|
|
#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(Word *memory, const char *str, uint32_t length,
|
|
uint32_t dest_addr) {
|
|
memory[dest_addr].u = length;
|
|
uint32_t buffer_addr = dest_addr + 1;
|
|
uint32_t i;
|
|
for (i = 0; i < length; i++) {
|
|
memory[buffer_addr + (i / 4)].c[i % 4] = str[i];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Step to the next opcode in the vm.
|
|
*/
|
|
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;
|
|
uint32_t dest_addr = memory[pc + 3].u;
|
|
pc += 4; /* Advance to next instruction */
|
|
|
|
if (src1_addr >= memory_size || src2_addr >= memory_size ||
|
|
dest_addr >= memory_size) {
|
|
printf("Invalid memory address!\n");
|
|
return 0;
|
|
}
|
|
|
|
switch (opcode) {
|
|
case OP_HALT:
|
|
return 0;
|
|
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_UINT:
|
|
memory[dest_addr].u = memory[src1_addr].u - memory[src2_addr].u;
|
|
return pc;
|
|
|
|
case OP_MUL_UINT:
|
|
memory[dest_addr].u = memory[src1_addr].u * memory[src2_addr].u;
|
|
return pc;
|
|
|
|
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].q = memory[src1_addr].q + memory[src2_addr].q;
|
|
return pc;
|
|
|
|
case OP_SUB_REAL:
|
|
memory[dest_addr].q = memory[src1_addr].q - memory[src2_addr].q;
|
|
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;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* 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_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: {
|
|
COMPARE_AND_JUMP(int32_t, i, ==);
|
|
}
|
|
case OP_JGT_INT: {
|
|
COMPARE_AND_JUMP(int32_t, i, >);
|
|
}
|
|
case OP_JLT_INT: {
|
|
COMPARE_AND_JUMP(int32_t, i, <);
|
|
}
|
|
case OP_JLE_INT: {
|
|
COMPARE_AND_JUMP(int32_t, i, <=);
|
|
}
|
|
case OP_JGE_INT: {
|
|
COMPARE_AND_JUMP(int32_t, i, >=);
|
|
}
|
|
case OP_JEQ_REAL: {
|
|
COMPARE_AND_JUMP(int32_t, i, ==);
|
|
}
|
|
case OP_JGT_REAL: {
|
|
COMPARE_AND_JUMP(int32_t, u, >);
|
|
}
|
|
case OP_JLT_REAL: {
|
|
COMPARE_AND_JUMP(int32_t, u, <);
|
|
}
|
|
case OP_JGE_REAL: {
|
|
COMPARE_AND_JUMP(int32_t, u, >=);
|
|
}
|
|
case OP_JLE_REAL: {
|
|
COMPARE_AND_JUMP(int32_t, u, <=);
|
|
}
|
|
case OP_INT_TO_STRING: {
|
|
int32_t v = memory[src1_addr].i;
|
|
char buffer[MAX_LEN_INT32];
|
|
int32_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_UINT_TO_STRING: {
|
|
uint32_t v = memory[src1_addr].u;
|
|
char buffer[MAX_LEN_INT32];
|
|
uint32_t n = v;
|
|
int i = MAX_LEN_INT32;
|
|
do {
|
|
buffer[--i] = radix_set[n % 10];
|
|
n /= 10;
|
|
} while (n > 0);
|
|
/* 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: {
|
|
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: {
|
|
uint32_t buffer_addr = dest_addr + 1;
|
|
uint32_t length = 0;
|
|
while (1) {
|
|
int ch = getchar();
|
|
if (ch == '\n' || ch == EOF) {
|
|
memory[buffer_addr + (length / 4)].c[length % 4] = '\0';
|
|
break;
|
|
}
|
|
memory[buffer_addr + (length / 4)].c[length % 4] = ch;
|
|
length++;
|
|
}
|
|
memory[dest_addr].u = length;
|
|
return pc;
|
|
}
|
|
case OP_PRINT_STRING: {
|
|
uint32_t string_addr = src1_addr;
|
|
uint32_t length = memory[src1_addr - 1].u;
|
|
uint32_t i;
|
|
for (i = 0; i < length; i++) {
|
|
uint8_t ch = memory[string_addr + (i / 4)].c[i % 4];
|
|
if (ch == '\0')
|
|
break;
|
|
putchar(ch);
|
|
}
|
|
putchar('\n');
|
|
return pc;
|
|
}
|
|
case OP_CMP_STRING: {
|
|
uint32_t addr1 = src1_addr;
|
|
uint32_t addr2 = src2_addr;
|
|
uint32_t length1 = memory[src1_addr - 1].u;
|
|
uint32_t length2 = memory[src2_addr - 1].u;
|
|
uint32_t equal = 1;
|
|
|
|
if (length1 != length2) {
|
|
equal = 0;
|
|
} else {
|
|
uint32_t i = 0;
|
|
while (i < length1) {
|
|
uint32_t char1 = memory[addr1 + i].u;
|
|
uint32_t char2 = memory[addr2 + i].u;
|
|
if (char1 != char2) {
|
|
equal = 0;
|
|
break;
|
|
}
|
|
if ((char1 & 0xFF) == '\0' && (char2 & 0xFF) == '\0')
|
|
return pc;
|
|
}
|
|
}
|
|
memory[dest_addr].u = equal;
|
|
return pc;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|