714 lines
15 KiB
C
714 lines
15 KiB
C
#include "vm.h"
|
|
#define FRAME_HEADER_SIZE 12
|
|
|
|
u32 pc; /* program counter */
|
|
u32 cp; /* code pointer */
|
|
u32 mp; /* memory pointer */
|
|
u32 fp; /* frame pointer */
|
|
u8 lc; /* child local count */
|
|
u8 status; /* status flag */
|
|
u8 interrupt; /* device interrupt */
|
|
u32 *code; /* code */
|
|
u8 *mem; /* memory */
|
|
|
|
#define MAX_LEN_INT32 11
|
|
#define MAX_INT32 2147483647
|
|
#define MIN_INT32 -2147483648
|
|
const char radix_set[11] = "0123456789";
|
|
|
|
u32 str_alloc(char *str, u32 length) {
|
|
u32 str_addr = mp;
|
|
u8 *ptr = &mem[mp];
|
|
mcpy(ptr, &length, sizeof(u32));
|
|
ptr += 4;
|
|
mcpy(ptr, str, length);
|
|
ptr[length] = '\0';
|
|
mp += 4 + length;
|
|
return str_addr;
|
|
}
|
|
|
|
bool step_vm() {
|
|
u32 instruction = code[pc++];
|
|
u8 opcode = DECODE_OP(instruction);
|
|
u32 *locals = (u32*)(&mem[fp]);
|
|
u32 *globals = (u32*)(mem);
|
|
|
|
switch (opcode) {
|
|
case OP_HALT: {
|
|
/* no need to decode, all are zeros */
|
|
return false;
|
|
}
|
|
case OP_CALL: {
|
|
DECODE_A(instruction)
|
|
/* function to jump to */
|
|
u32 fn_ptr = locals[dest];
|
|
/* get mp in 'global indexing mode' */
|
|
u32 *header = &globals[mp / 4];
|
|
/* reset child locals counter */
|
|
lc = 0;
|
|
/* push parents frame value to reset the heap to */
|
|
(*header++) = fp;
|
|
/* push return address to child frame */
|
|
(*header++) = pc;
|
|
/* push local address to return the value to */
|
|
(*header++) = fp + (src2 * 4);
|
|
/* increase the mp to new size */
|
|
mp += FRAME_HEADER_SIZE;
|
|
/* now set the frame pointer, where the locals start */
|
|
fp = mp;
|
|
/* move mp forward by count many locals */
|
|
mp += (src1 * 4);
|
|
/* jump to dest_ptr */
|
|
pc = fn_ptr;
|
|
return true;
|
|
}
|
|
case OP_RETURN: {
|
|
DECODE_B(instruction)
|
|
u32 size = 0;
|
|
u32 return_value = locals[dest];
|
|
bool is_ptr = (((u32)(1)) << 15) & imm;
|
|
bool replaces_value = (((u32)(1)) << 14) & imm;
|
|
|
|
/* reset mp to saved mp, use header size to get "real" start of frame */
|
|
u32 *frame_start = &globals[(fp / 4) - 3];
|
|
u32 parent_fp = *frame_start++;
|
|
u32 return_address = *frame_start++;
|
|
u32 parent_local_return_address = *frame_start++;
|
|
|
|
USED(replaces_value);
|
|
/* reset memory to parents end of memory */
|
|
mp = fp - FRAME_HEADER_SIZE;
|
|
/* reset the frame pointer */
|
|
fp = parent_fp;
|
|
|
|
if (is_ptr) {
|
|
/* copy value to end of mp if it is a pointer */
|
|
globals[parent_local_return_address/4] = mp;
|
|
size = globals[return_value/4];
|
|
globals[mp/4] = size;
|
|
mp += 4;
|
|
mcpy(&mem[mp], &mem[return_value], size);
|
|
mp += size;
|
|
} else {
|
|
/* otherwise just write the return value to its location */
|
|
mcpy(&mem[parent_local_return_address], &return_value, sizeof(u32));
|
|
}
|
|
|
|
/* jump to parent frame */
|
|
pc = return_address;
|
|
return true;
|
|
}
|
|
case OP_SYSCALL: {
|
|
DECODE_A(instruction)
|
|
u32 id = dest; /* syscall id */
|
|
u32 size = src1; /* size of heap at that pointer */
|
|
u32 rd = locals[src2]; /* the pointer */
|
|
status = syscall(id, size, rd);
|
|
return true;
|
|
}
|
|
case OP_PUSH: {
|
|
DECODE_B(instruction)
|
|
USED(imm);
|
|
globals[(mp / 4) + lc + 3] = locals[dest];
|
|
lc++;
|
|
return true;
|
|
}
|
|
case OP_POP: {
|
|
DECODE_B(instruction)
|
|
USED(dest);
|
|
USED(imm);
|
|
mp -= 4;
|
|
lc--;
|
|
return true;
|
|
}
|
|
case OP_LOAD_IMM: {
|
|
DECODE_B(instruction)
|
|
locals[dest] = imm;
|
|
return true;
|
|
}
|
|
case OP_LOAD_UPPER_IMM: {
|
|
DECODE_B(instruction)
|
|
u32 value = locals[dest];
|
|
locals[dest] = (value | (((u32)(imm)) << 16));
|
|
return true;
|
|
}
|
|
case OP_LOAD_IND_8: {
|
|
DECODE_A(instruction)
|
|
USED(src2);
|
|
locals[dest] = READ_U8(locals[src1]);
|
|
return true;
|
|
}
|
|
case OP_LOAD_IND_16: {
|
|
DECODE_A(instruction)
|
|
USED(src2);
|
|
locals[dest] = READ_U16(locals[src1]);
|
|
return true;
|
|
}
|
|
case OP_LOAD_IND_32: {
|
|
DECODE_A(instruction)
|
|
USED(src2);
|
|
locals[dest] = READ_U32(locals[src1]);
|
|
return true;
|
|
}
|
|
case OP_LOAD_ABS_8: {
|
|
/* need multibyte for this, ignore for now */
|
|
status = 250;
|
|
return false;
|
|
}
|
|
case OP_LOAD_ABS_16: {
|
|
/* need multibyte for this, ignore for now */
|
|
status = 250;
|
|
return false;
|
|
}
|
|
case OP_LOAD_ABS_32: {
|
|
/* need multibyte for this, ignore for now */
|
|
status = 250;
|
|
return false;
|
|
}
|
|
case OP_LOAD_OFF_8: {
|
|
DECODE_A(instruction)
|
|
locals[dest] = READ_U8((locals[src1] + locals[src2]));
|
|
return true;
|
|
}
|
|
case OP_LOAD_OFF_16: {
|
|
DECODE_A(instruction)
|
|
locals[dest] = READ_U16((locals[src1] + locals[src2]));
|
|
return true;
|
|
}
|
|
case OP_LOAD_OFF_32: {
|
|
DECODE_A(instruction)
|
|
locals[dest] = READ_U32((locals[src1] + locals[src2]));
|
|
return true;
|
|
}
|
|
case OP_STORE_ABS_8: {
|
|
/* need multibyte for this, ignore for now */
|
|
status = 250;
|
|
return false;
|
|
}
|
|
case OP_STORE_ABS_16: {
|
|
/* need multibyte for this, ignore for now */
|
|
status = 250;
|
|
return false;
|
|
}
|
|
case OP_STORE_ABS_32: {
|
|
/* need multibyte for this, ignore for now */
|
|
status = 250;
|
|
return false;
|
|
}
|
|
case OP_STORE_IND_8: {
|
|
DECODE_A(instruction)
|
|
USED(src2);
|
|
WRITE_U8(locals[dest], locals[src1]);
|
|
return true;
|
|
}
|
|
case OP_STORE_IND_16: {
|
|
DECODE_A(instruction)
|
|
USED(src2);
|
|
WRITE_U16(locals[dest], locals[src1]);
|
|
return true;
|
|
}
|
|
case OP_STORE_IND_32: {
|
|
DECODE_A(instruction)
|
|
USED(src2);
|
|
WRITE_U32(locals[dest], locals[src1]);
|
|
return true;
|
|
}
|
|
case OP_STORE_OFF_8: {
|
|
DECODE_A(instruction)
|
|
WRITE_U8((locals[dest] + locals[src2]), locals[src1]);
|
|
return true;
|
|
}
|
|
case OP_STORE_OFF_16: {
|
|
DECODE_A(instruction)
|
|
WRITE_U16((locals[dest] + locals[src2]), locals[src1]);
|
|
return true;
|
|
}
|
|
case OP_STORE_OFF_32: {
|
|
DECODE_A(instruction)
|
|
WRITE_U32((locals[dest] + locals[src2]), locals[src1]);
|
|
return true;
|
|
}
|
|
case OP_MEM_ALLOC: {
|
|
DECODE_A(instruction)
|
|
u32 size = locals[src1];
|
|
locals[dest] = mp;
|
|
WRITE_U32(mp, size);
|
|
USED(src2);
|
|
mp += (size + 4);
|
|
return true;
|
|
}
|
|
case OP_MEM_CPY_8: {
|
|
DECODE_A(instruction)
|
|
u32 i = 0;
|
|
u32 mdest = locals[dest];
|
|
u32 msrc = locals[src1];
|
|
u32 count = locals[src2];
|
|
|
|
if (mdest + count >= mp) {
|
|
status = 1;
|
|
return true;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
mem[msrc + i] = mem[mdest + i];
|
|
}
|
|
|
|
status = 0;
|
|
return true;
|
|
}
|
|
case OP_MEM_CPY_16: {
|
|
DECODE_A(instruction)
|
|
u32 i = 0;
|
|
u32 mdest = locals[dest];
|
|
u32 msrc = locals[src1];
|
|
u32 count = locals[src2] * 2;
|
|
|
|
if (mdest + count >= mp) {
|
|
status = 1;
|
|
return true;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
u16 value = READ_U16(mdest + i);
|
|
WRITE_U16(msrc + i, value);
|
|
}
|
|
|
|
status = 0;
|
|
return true;
|
|
}
|
|
case OP_MEM_CPY_32: {
|
|
DECODE_A(instruction)
|
|
u32 i = 0;
|
|
u32 mdest = locals[dest];
|
|
u32 msrc = locals[src1];
|
|
u32 count = locals[src2];
|
|
|
|
if (mdest + count >= mp) {
|
|
status = 1;
|
|
return true;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
globals[msrc + i] = globals[mdest + i];
|
|
}
|
|
|
|
status = 0;
|
|
return true;
|
|
}
|
|
case OP_MEM_SET_8: {
|
|
DECODE_A(instruction)
|
|
u32 i, start, end;
|
|
|
|
u32 rd = fp + (dest * 4);
|
|
u32 r1 = fp + (src1 * 4);
|
|
u32 r2 = fp + (src2 * 4);
|
|
|
|
u8 value = (u8)READ_U32(r1);
|
|
u32 count = READ_U32(r2);
|
|
|
|
if (r2 == 0) {
|
|
status = 1;
|
|
return true;
|
|
}
|
|
|
|
start = READ_U32(rd);
|
|
end = start + count;
|
|
|
|
if (start >= mp || r2 > mp || end > mp) {
|
|
status = 1;
|
|
return true;
|
|
}
|
|
|
|
for (i = start; i < end; i++) {
|
|
mem[i] = value;
|
|
}
|
|
|
|
status = 0;
|
|
return true;
|
|
}
|
|
case OP_MEM_SET_16: {
|
|
DECODE_A(instruction)
|
|
u32 i, start, end;
|
|
|
|
u32 rd = fp + (dest * 4);
|
|
u32 r1 = fp + (src1 * 4);
|
|
u32 r2 = fp + (src2 * 4);
|
|
|
|
u16 value = (u16)READ_U32(r1);
|
|
u32 count = READ_U32(r2);
|
|
|
|
if (r2 == 0) {
|
|
status = 1;
|
|
return true;
|
|
}
|
|
|
|
start = READ_U32(rd);
|
|
end = start + count;
|
|
|
|
if (start >= mp || r2 > mp || end > mp) {
|
|
status = 1;
|
|
return true;
|
|
}
|
|
|
|
for (i = start; i < end; i += 2) {
|
|
WRITE_U16(i, value);
|
|
}
|
|
|
|
status = 0;
|
|
return true;
|
|
}
|
|
case OP_MEM_SET_32: {
|
|
DECODE_A(instruction)
|
|
u32 i, start, end;
|
|
|
|
u32 value = locals[src1];
|
|
u32 count = locals[src2];
|
|
|
|
if (count == 0) {
|
|
status = 1;
|
|
return true;
|
|
}
|
|
|
|
start = READ_U32(locals[dest]);
|
|
end = start + count;
|
|
|
|
if (start >= mp || count > mp || end > mp) {
|
|
status = 1;
|
|
return true;
|
|
}
|
|
|
|
for (i = start; i < end; i += 4) {
|
|
WRITE_U32(i, value);
|
|
}
|
|
|
|
status = 0;
|
|
return true;
|
|
}
|
|
case OP_MOV: {
|
|
DECODE_A(instruction)
|
|
USED(src2);
|
|
locals[dest] = locals[src1];
|
|
return true;
|
|
}
|
|
case OP_ADD_INT: {
|
|
MATH_OP(i32, +);
|
|
}
|
|
case OP_SUB_INT: {
|
|
MATH_OP(i32, -);
|
|
}
|
|
case OP_MUL_INT: {
|
|
MATH_OP(i32, *);
|
|
}
|
|
case OP_DIV_INT: {
|
|
MATH_OP(i32, /);
|
|
}
|
|
case OP_ADD_NAT: {
|
|
MATH_OP(u32, +);
|
|
}
|
|
case OP_SUB_NAT: {
|
|
MATH_OP(u32, -);
|
|
}
|
|
case OP_MUL_NAT: {
|
|
MATH_OP(u32, *);
|
|
}
|
|
case OP_DIV_NAT: {
|
|
MATH_OP(u32, /);
|
|
}
|
|
case OP_ADD_REAL: {
|
|
MATH_OP(i32, +);
|
|
}
|
|
case OP_SUB_REAL: {
|
|
MATH_OP(i32, -);
|
|
}
|
|
case OP_MUL_REAL: {
|
|
DECODE_A(instruction)
|
|
|
|
i32 src1_whole = (i32)locals[src1] >> 16;
|
|
i32 src2_whole = (i32)locals[src2] >> 16;
|
|
|
|
i32 src1_decimal = (i32)locals[src1] & 16;
|
|
i32 src2_decimal = (i32)locals[src2] & 16;
|
|
|
|
i32 result = 0;
|
|
result += (src1_whole * src2_whole) << 16;
|
|
result += (src1_whole * src2_decimal);
|
|
result += (src1_decimal * src2_whole);
|
|
result += ((src1_decimal * src2_decimal) >> 16) & 16;
|
|
locals[dest] = result;
|
|
return true;
|
|
}
|
|
case OP_DIV_REAL: {
|
|
DECODE_A(instruction)
|
|
i32 result;
|
|
i32 src1_val = (i32)locals[src1];
|
|
i32 src2_val = (i32)locals[src2];
|
|
|
|
u32 src2_reciprocal = 1;
|
|
src2_reciprocal <<= 31;
|
|
src2_reciprocal = (u32)(src2_reciprocal / src2_val);
|
|
|
|
result = src1_val * src2_reciprocal;
|
|
result <<= 1;
|
|
|
|
locals[dest] = result;
|
|
return true;
|
|
}
|
|
case OP_INT_TO_REAL: {
|
|
DECODE_A(instruction)
|
|
i32 result = (i32)locals[src1] << 16;
|
|
USED(src2);
|
|
locals[dest] = result;
|
|
return true;
|
|
}
|
|
case OP_INT_TO_NAT: {
|
|
DECODE_A(instruction)
|
|
u32 result = (u32)locals[src1];
|
|
USED(src2);
|
|
locals[dest] = result;
|
|
return true;
|
|
}
|
|
case OP_NAT_TO_REAL: {
|
|
DECODE_A(instruction)
|
|
i32 result = (i32)locals[src1] << 16;
|
|
USED(src2);
|
|
locals[dest] = result;
|
|
return true;
|
|
}
|
|
case OP_NAT_TO_INT: {
|
|
DECODE_A(instruction)
|
|
i32 result = (i32)locals[src1];
|
|
USED(src2);
|
|
locals[dest] = result;
|
|
return true;
|
|
}
|
|
case OP_REAL_TO_INT: {
|
|
DECODE_A(instruction)
|
|
i32 result = (i32)locals[src1] >> 16;
|
|
USED(src2);
|
|
locals[dest] = result;
|
|
return true;
|
|
}
|
|
case OP_REAL_TO_NAT: {
|
|
DECODE_A(instruction)
|
|
u32 result = (u32)locals[src1] >> 16;
|
|
USED(src2);
|
|
locals[dest] = result;
|
|
return true;
|
|
}
|
|
case OP_BIT_SHIFT_LEFT: {
|
|
MATH_OP_NO_CAST(<<);
|
|
}
|
|
case OP_BIT_SHIFT_RIGHT: {
|
|
MATH_OP_NO_CAST(>>);
|
|
}
|
|
case OP_BIT_SHIFT_R_EXT: {
|
|
MATH_OP(i32, >>);
|
|
}
|
|
case OP_BIT_AND: {
|
|
MATH_OP_NO_CAST(&);
|
|
}
|
|
case OP_BIT_OR: {
|
|
MATH_OP_NO_CAST(|);
|
|
}
|
|
case OP_BIT_XOR: {
|
|
MATH_OP_NO_CAST(^);
|
|
}
|
|
case OP_JMP_IMM: {
|
|
u32 imm = (((u32)code[(pc) + 3] << 24) |
|
|
((u32)code[(pc) + 2] << 16) |
|
|
((u32)code[(pc) + 1] << 8) |
|
|
((u32)code[(pc)]));
|
|
pc = imm;
|
|
return true;
|
|
}
|
|
case OP_JMP_ABS: {
|
|
DECODE_A(instruction)
|
|
u32 jmp_dest = locals[dest];
|
|
if (jmp_dest > cp) {
|
|
status = 1;
|
|
return true;
|
|
}
|
|
USED(src1);
|
|
USED(src2);
|
|
|
|
pc = jmp_dest;
|
|
return true;
|
|
}
|
|
case OP_JMP_OFF: {
|
|
DECODE_A(instruction)
|
|
u32 jmp_dest = locals[dest] + locals[src1];
|
|
if (jmp_dest > cp) {
|
|
status = 1;
|
|
return true;
|
|
}
|
|
USED(src2);
|
|
|
|
pc = jmp_dest;
|
|
return true;
|
|
}
|
|
case OP_JMP_FLAG: {
|
|
DECODE_A(instruction)
|
|
u32 mask;
|
|
u32 jmp_dest = locals[dest];
|
|
if (jmp_dest > cp) {
|
|
status = 1;
|
|
return true;
|
|
}
|
|
USED(src1);
|
|
USED(src2);
|
|
|
|
mask = -(u32)(status == 0);
|
|
pc = (jmp_dest & mask) | (pc & ~mask);
|
|
return true;
|
|
}
|
|
case OP_JEQ_INT: {
|
|
COMPARE_AND_JUMP(i32, ==);
|
|
}
|
|
case OP_JNE_INT: {
|
|
COMPARE_AND_JUMP(i32, !=);
|
|
}
|
|
case OP_JGT_INT: {
|
|
COMPARE_AND_JUMP(i32, >);
|
|
}
|
|
case OP_JLT_INT: {
|
|
COMPARE_AND_JUMP(i32, <);
|
|
}
|
|
case OP_JLE_INT: {
|
|
COMPARE_AND_JUMP(i32, <=);
|
|
}
|
|
case OP_JGE_INT: {
|
|
COMPARE_AND_JUMP(i32, >=);
|
|
}
|
|
case OP_JEQ_NAT: {
|
|
COMPARE_AND_JUMP(u32, ==);
|
|
}
|
|
case OP_JNE_NAT: {
|
|
COMPARE_AND_JUMP(u32, !=);
|
|
}
|
|
case OP_JGT_NAT: {
|
|
COMPARE_AND_JUMP(u32, >);
|
|
}
|
|
case OP_JLT_NAT: {
|
|
COMPARE_AND_JUMP(u32, <);
|
|
}
|
|
case OP_JLE_NAT: {
|
|
COMPARE_AND_JUMP(u32, <=);
|
|
}
|
|
case OP_JGE_NAT: {
|
|
COMPARE_AND_JUMP(u32, >=);
|
|
}
|
|
case OP_JEQ_REAL: {
|
|
COMPARE_AND_JUMP(i32, ==);
|
|
}
|
|
case OP_JNE_REAL: {
|
|
COMPARE_AND_JUMP(i32, !=);
|
|
}
|
|
case OP_JGE_REAL: {
|
|
COMPARE_AND_JUMP(i32, >=);
|
|
}
|
|
case OP_JGT_REAL: {
|
|
COMPARE_AND_JUMP(i32, >);
|
|
}
|
|
case OP_JLT_REAL: {
|
|
COMPARE_AND_JUMP(i32, <);
|
|
}
|
|
case OP_JLE_REAL: {
|
|
COMPARE_AND_JUMP(i32, <=);
|
|
}
|
|
case OP_INT_TO_STR: {
|
|
DECODE_A(instruction)
|
|
|
|
u32 i = MAX_LEN_INT32;
|
|
i32 v = (i32)locals[src1];
|
|
char buffer[MAX_LEN_INT32];
|
|
i32 n = v;
|
|
bool neg = n < 0;
|
|
USED(src2);
|
|
|
|
if (neg)
|
|
n = -n;
|
|
|
|
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 */
|
|
locals[dest] = str_alloc(buffer + i, MAX_LEN_INT32 - i);
|
|
|
|
return pc;
|
|
}
|
|
case OP_NAT_TO_STR: {
|
|
DECODE_A(instruction)
|
|
|
|
u32 v = (i32)locals[src1];
|
|
char buffer[MAX_LEN_INT32];
|
|
u32 n = v;
|
|
u32 i = MAX_LEN_INT32;
|
|
USED(src2);
|
|
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 */
|
|
locals[dest] = str_alloc(buffer + i, MAX_LEN_INT32 - i);
|
|
|
|
return pc;
|
|
}
|
|
case OP_REAL_TO_STR: {
|
|
DECODE_A(instruction)
|
|
|
|
u32 i = 0, j = 0;
|
|
i32 q = (i32)locals[src1];
|
|
char buffer[MAX_LEN_INT32];
|
|
u32 int_part, frac_part;
|
|
|
|
if (q < 0) {
|
|
buffer[i++] = '-';
|
|
q = -q;
|
|
}
|
|
|
|
int_part = q >> 16;
|
|
frac_part = q & 0xFFFF;
|
|
USED(src2);
|
|
|
|
if (int_part == 0) {
|
|
buffer[i++] = radix_set[0];
|
|
} else {
|
|
char tmp[16];
|
|
i32 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];
|
|
}
|
|
}
|
|
|
|
buffer[i++] = '.';
|
|
for (j = 0; j < 6; j++) {
|
|
frac_part *= 10;
|
|
buffer[i++] = radix_set[frac_part >> 16];
|
|
frac_part &= 0xFFFF;
|
|
}
|
|
|
|
locals[dest] = str_alloc(buffer + i, MAX_LEN_INT32 - i);
|
|
|
|
return pc;
|
|
}
|
|
}
|
|
|
|
/* something went very wrong */
|
|
status = 255;
|
|
return false;
|
|
}
|