The macros used in the testing library are actually useful everywhere so may as well use them.
704 lines
26 KiB
C
704 lines
26 KiB
C
/* Copyright (C) 2023, 2024 Aryadev Chavali
|
|
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
|
|
* more details.
|
|
|
|
* You should have received a copy of the GNU General Public License Version 2
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
* Created: 2023-10-15
|
|
* Author: Aryadev Chavali
|
|
* Description: Virtual machine implementation
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#if VERBOSE >= 2
|
|
#include <string.h>
|
|
#endif
|
|
|
|
#include <vm/runtime.h>
|
|
|
|
const char *err_as_cstr(err_t err)
|
|
{
|
|
switch (err)
|
|
{
|
|
case ERR_OK:
|
|
return "OK";
|
|
case ERR_STACK_UNDERFLOW:
|
|
return "STACK_UNDERFLOW";
|
|
case ERR_STACK_OVERFLOW:
|
|
return "STACK_OVERFLOW";
|
|
case ERR_CALL_STACK_UNDERFLOW:
|
|
return "CALL_STACK_UNDERFLOW";
|
|
case ERR_CALL_STACK_OVERFLOW:
|
|
return "CALL_STACK_OVERFLOW";
|
|
case ERR_INVALID_OPCODE:
|
|
return "INVALID_OPCODE";
|
|
case ERR_INVALID_REGISTER_BYTE:
|
|
return "INVALID_REGISTER_BYTE";
|
|
case ERR_INVALID_REGISTER_SHORT:
|
|
return "INVALID_REGISTER_SHORT";
|
|
case ERR_INVALID_REGISTER_HWORD:
|
|
return "INVALID_REGISTER_HWORD";
|
|
case ERR_INVALID_REGISTER_WORD:
|
|
return "INVALID_REGISTER_WORD";
|
|
case ERR_INVALID_PROGRAM_ADDRESS:
|
|
return "INVALID_PROGRAM_ADDRESS";
|
|
case ERR_INVALID_PAGE_ADDRESS:
|
|
return "INVALID_PAGE_ADDRESS";
|
|
case ERR_OUT_OF_BOUNDS:
|
|
return "OUT_OF_BOUNDS";
|
|
case ERR_END_OF_PROGRAM:
|
|
return "END_OF_PROGRAM";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
static_assert(NUMBER_OF_OPCODES == 115, "vm_execute: Out of date");
|
|
|
|
err_t vm_execute(vm_t *vm)
|
|
{
|
|
struct Program *prog = &vm->program;
|
|
prog_t program_data = prog->data;
|
|
if (prog->ptr >= program_data.count)
|
|
return ERR_END_OF_PROGRAM;
|
|
inst_t instruction = program_data.instructions[prog->ptr];
|
|
|
|
// Opcodes which defer to another function using lookup table
|
|
if (UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_PUSH))
|
|
{
|
|
err_t err = PUSH_ROUTINES[instruction.opcode](vm, instruction.operand);
|
|
if (err)
|
|
return err;
|
|
prog->ptr++;
|
|
}
|
|
else if (UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MOV) ||
|
|
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_PUSH_REGISTER) ||
|
|
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_DUP) ||
|
|
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MALLOC) ||
|
|
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MSET) ||
|
|
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MGET))
|
|
{
|
|
err_t err =
|
|
WORD_ROUTINES[instruction.opcode](vm, instruction.operand.as_word);
|
|
if (err)
|
|
return err;
|
|
prog->ptr++;
|
|
}
|
|
else if (UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_POP))
|
|
{
|
|
static_assert(DATA_TYPE_NIL == -1 && DATA_TYPE_WORD == 3,
|
|
"Code using OPCODE_DATA_TYPE for quick same type opcode "
|
|
"conversion may be out of date.");
|
|
|
|
// NOTE: We always use the first register to hold the result of this pop.
|
|
|
|
// Here we add OP_MOV_BYTE and the data_type_t of the opcode to get the
|
|
// right typed OP_MOV opcode.
|
|
opcode_t mov_opcode =
|
|
OPCODE_DATA_TYPE(instruction.opcode, OP_POP) + OP_MOV_BYTE;
|
|
|
|
err_t err = WORD_ROUTINES[mov_opcode](vm, 0);
|
|
if (err)
|
|
return err;
|
|
prog->ptr++;
|
|
}
|
|
else if (UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_NOT) ||
|
|
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_OR) ||
|
|
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_AND) ||
|
|
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_XOR) ||
|
|
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_EQ) ||
|
|
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_PLUS) ||
|
|
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_SUB) ||
|
|
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MULT) ||
|
|
SIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_LT) ||
|
|
SIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_LTE) ||
|
|
SIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_GT) ||
|
|
SIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_GTE) ||
|
|
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MALLOC) ||
|
|
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MSET) ||
|
|
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MGET) ||
|
|
instruction.opcode == OP_MDELETE || instruction.opcode == OP_MSIZE)
|
|
{
|
|
err_t err = STACK_ROUTINES[instruction.opcode](vm);
|
|
if (err)
|
|
return err;
|
|
prog->ptr++;
|
|
}
|
|
// Opcodes defined in loop
|
|
else if (instruction.opcode == OP_JUMP_ABS)
|
|
return vm_jump(vm, instruction.operand.as_word);
|
|
else if (UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_JUMP_IF))
|
|
{
|
|
static_assert(DATA_TYPE_NIL == -1 && DATA_TYPE_WORD == 3,
|
|
"Code using OPCODE_DATA_TYPE for quick same type opcode "
|
|
"conversion may be out of date.");
|
|
|
|
data_t datum = {0};
|
|
// Here we add OP_POP_BYTE and the data_type_t of the opcode to get the
|
|
// right OP_POP opcode.
|
|
opcode_t pop_opcode =
|
|
OPCODE_DATA_TYPE(instruction.opcode, OP_JUMP_IF) + OP_POP_BYTE;
|
|
|
|
err_t err = POP_ROUTINES[pop_opcode](vm, &datum);
|
|
if (err)
|
|
return err;
|
|
|
|
// If datum != 0 then jump, else go to the next instruction
|
|
if (datum.as_word != 0)
|
|
return vm_jump(vm, instruction.operand.as_word);
|
|
++prog->ptr;
|
|
}
|
|
else if (instruction.opcode == OP_CALL)
|
|
{
|
|
if (vm->call_stack.ptr >= vm->call_stack.max)
|
|
return ERR_CALL_STACK_OVERFLOW;
|
|
vm->call_stack.address_pointers[vm->call_stack.ptr++] = vm->program.ptr + 1;
|
|
return vm_jump(vm, instruction.operand.as_word);
|
|
}
|
|
else if (instruction.opcode == OP_RET)
|
|
{
|
|
if (vm->call_stack.ptr == 0)
|
|
return ERR_CALL_STACK_UNDERFLOW;
|
|
word_t addr = vm->call_stack.address_pointers[vm->call_stack.ptr - 1];
|
|
err_t err = vm_jump(vm, vm->call_stack.address_pointers[addr]);
|
|
if (err)
|
|
return err;
|
|
|
|
--vm->call_stack.ptr;
|
|
}
|
|
else if (SIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_PRINT))
|
|
{
|
|
static_assert(DATA_TYPE_NIL == -1 && DATA_TYPE_WORD == 3,
|
|
"Code using OPCODE_DATA_TYPE for quick same type opcode "
|
|
"conversion may be out of date.");
|
|
static_assert(OP_PRINT_SWORD - OP_PRINT_BYTE == 7,
|
|
"Implementation of OP_PRINT is out of date");
|
|
/* 1) Pop
|
|
2) Format
|
|
3) Print
|
|
*/
|
|
|
|
// 1) figure out what datum type to pop
|
|
|
|
// type in [0, 7] representing [byte, sbyte, short, sshort, hword, shword,
|
|
// word, sword]
|
|
int type = OPCODE_DATA_TYPE(instruction.opcode, OP_PRINT);
|
|
|
|
/* Byte and SByte -> POP_BYTE
|
|
Short and SShort -> POP_SHORT
|
|
HWord and SHword -> POP_HWORD
|
|
Word and SWord -> POP_WORD
|
|
*/
|
|
opcode_t pop_opcode = OP_POP_BYTE + (type / 2);
|
|
|
|
data_t datum = {0};
|
|
err_t err = POP_ROUTINES[pop_opcode](vm, &datum);
|
|
|
|
if (err)
|
|
return err;
|
|
|
|
// 2) create a format string for each datum type possible
|
|
|
|
// TODO: Figure out a way to ensure the ordering of OP_PRINT_* is exactly
|
|
// BYTE, SBYTE, SHORT, SSHORT, HWORD, SHWORD, WORD, SWORD via static_assert
|
|
|
|
// lookup table
|
|
const char *format_strings[] = {
|
|
"0x%X",
|
|
"%c",
|
|
#if PRINT_HEX == 1
|
|
"0x%X",
|
|
"0x%X",
|
|
"0x%X",
|
|
"0x%X",
|
|
"0x%lX",
|
|
"0x%dX",
|
|
#else
|
|
("%" PRIu32),
|
|
("%" PRId32),
|
|
("%" PRIu64),
|
|
("%" PRId64),
|
|
#endif
|
|
};
|
|
|
|
// 3) Print datum using the format string given.
|
|
printf(format_strings[type], datum);
|
|
|
|
prog->ptr++;
|
|
}
|
|
else if (instruction.opcode == OP_HALT)
|
|
{
|
|
// Do nothing here. Should be caught by callers of vm_execute
|
|
}
|
|
else
|
|
return ERR_INVALID_OPCODE;
|
|
return ERR_OK;
|
|
}
|
|
|
|
err_t vm_execute_all(vm_t *vm)
|
|
{
|
|
struct Program *program = &vm->program;
|
|
const size_t count = program->data.count;
|
|
err_t err = ERR_OK;
|
|
// Setup the initial address according to the program
|
|
program->ptr = program->data.start_address;
|
|
#if VERBOSE >= 1
|
|
size_t cycles = 0;
|
|
#endif
|
|
#if VERBOSE >= 2
|
|
struct Registers prev_registers = vm->registers;
|
|
size_t prev_sptr = 0;
|
|
size_t prev_pages = 0;
|
|
size_t prev_cptr = 0;
|
|
#endif
|
|
while (program->ptr < count &&
|
|
program->data.instructions[program->ptr].opcode != OP_HALT)
|
|
{
|
|
#if VERBOSE >= 2
|
|
INFO("vm_execute_all", "Trace(Cycle%lu)\n", cycles);
|
|
fputs(
|
|
"----------------------------------------------------------------------"
|
|
"----------\n",
|
|
stdout);
|
|
vm_print_program(vm, stdout);
|
|
fputs(
|
|
"----------------------------------------------------------------------"
|
|
"----------\n",
|
|
stdout);
|
|
if (prev_cptr != vm->call_stack.ptr)
|
|
{
|
|
vm_print_call_stack(vm, stdout);
|
|
prev_cptr = vm->call_stack.ptr;
|
|
fputs("------------------------------------------------------------------"
|
|
"----"
|
|
"----------\n",
|
|
stdout);
|
|
}
|
|
if (prev_pages != HEAP_SIZE(vm->heap))
|
|
{
|
|
vm_print_heap(vm, stdout);
|
|
prev_pages = HEAP_SIZE(vm->heap);
|
|
fputs("------------------------------------------------------------------"
|
|
"----"
|
|
"----------\n",
|
|
stdout);
|
|
}
|
|
if (memcmp(&prev_registers, &vm->registers, sizeof(vm->registers)) != 0)
|
|
{
|
|
vm_print_registers(vm, stdout);
|
|
prev_registers = vm->registers;
|
|
fputs("------------------------------------------------------------------"
|
|
"----"
|
|
"----------\n",
|
|
stdout);
|
|
}
|
|
if (prev_sptr != vm->stack.ptr)
|
|
{
|
|
vm_print_stack(vm, stdout);
|
|
prev_sptr = vm->stack.ptr;
|
|
fputs("------------------------------------------------------------------"
|
|
"----"
|
|
"----------\n",
|
|
stdout);
|
|
}
|
|
#endif
|
|
#if VERBOSE >= 1
|
|
++cycles;
|
|
#endif
|
|
err = vm_execute(vm);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
#if VERBOSE >= 1
|
|
INFO("vm_execute_all", "Final VM State(Cycle %lu)\n", cycles);
|
|
vm_print_all(vm, stdout);
|
|
#endif
|
|
return err;
|
|
}
|
|
|
|
err_t vm_jump(vm_t *vm, word_t w)
|
|
{
|
|
if (w >= vm->program.data.count)
|
|
return ERR_INVALID_PROGRAM_ADDRESS;
|
|
vm->program.ptr = w;
|
|
return ERR_OK;
|
|
}
|
|
|
|
err_t vm_push_byte(vm_t *vm, data_t b)
|
|
{
|
|
if (vm->stack.ptr >= vm->stack.max)
|
|
return ERR_STACK_OVERFLOW;
|
|
vm->stack.data[vm->stack.ptr++] = b.as_byte;
|
|
return ERR_OK;
|
|
}
|
|
|
|
/* Pushing value onto the stack
|
|
|
|
Convert the datum into LE ordering then push onto the stack byte by
|
|
byte. This means that the MSB of any datum is at the top of the
|
|
stack by the end of the procedure. If we look at the stack from
|
|
top down the number is in big endian, but from bottom up the number
|
|
is in little endian.
|
|
|
|
This is the same as just converting some datum into LE ordered
|
|
bytes, which convert_<TYPE>_to_bytes does.
|
|
|
|
Consider halfword 0x89ABCDEF.
|
|
1) Bytes are {0xEF, 0xCD, 0xAB, 0x89}
|
|
2) Stack top is 0x89 */
|
|
#define VM_PUSH_CONSTR(TYPE, TYPE_CAP) \
|
|
err_t vm_push_##TYPE(vm_t *vm, data_t f) \
|
|
{ \
|
|
if (vm->stack.ptr + TYPE_CAP##_SIZE >= vm->stack.max) \
|
|
return ERR_STACK_OVERFLOW; \
|
|
convert_##TYPE##_to_bytes(f.as_##TYPE, vm->stack.data + vm->stack.ptr); \
|
|
vm->stack.ptr += TYPE_CAP##_SIZE; \
|
|
return ERR_OK; \
|
|
}
|
|
|
|
VM_PUSH_CONSTR(short, SHORT)
|
|
VM_PUSH_CONSTR(hword, HWORD)
|
|
VM_PUSH_CONSTR(word, WORD)
|
|
|
|
err_t vm_pop_byte(vm_t *vm, data_t *ret)
|
|
{
|
|
if (vm->stack.ptr == 0)
|
|
return ERR_STACK_UNDERFLOW;
|
|
*ret = DBYTE(vm->stack.data[--vm->stack.ptr]);
|
|
return ERR_OK;
|
|
}
|
|
|
|
/* Popping a value from the stack and storing it
|
|
|
|
Since data is pushed in little endian format onto the stack, such
|
|
that the MSB is the top of the stack, copying N bytes from (top -
|
|
N) to N gives us the exact bytes of the datum in little endian.
|
|
|
|
Convert that into host order and we're done.
|
|
|
|
Consider halfword 0x89ABCDEF. When pushed onto the stack, looking
|
|
at the stack from bottom up there are the values {0xEF, 0xCD, 0xAB,
|
|
0x89} where 0x89 is at the top of the stack.
|
|
|
|
1) Bytes are copied into buffer {0xEF, 0xCD, 0xAB, 0x89}
|
|
2) Value is converted into host order datum */
|
|
|
|
#define VM_POP_CONSTR(TYPE, TYPE_CAP) \
|
|
err_t vm_pop_##TYPE(vm_t *vm, data_t *ret) \
|
|
{ \
|
|
if (vm->stack.ptr < TYPE_CAP##_SIZE) \
|
|
return ERR_STACK_UNDERFLOW; \
|
|
byte_t bytes[TYPE_CAP##_SIZE] = {0}; \
|
|
memcpy(bytes, vm->stack.data + (vm->stack.ptr - TYPE_CAP##_SIZE), \
|
|
TYPE_CAP##_SIZE); \
|
|
*ret = D##TYPE_CAP(convert_bytes_to_##TYPE(bytes)); \
|
|
return ERR_OK; \
|
|
}
|
|
|
|
VM_POP_CONSTR(short, SHORT)
|
|
VM_POP_CONSTR(hword, HWORD)
|
|
VM_POP_CONSTR(word, WORD)
|
|
|
|
/* Pushing the value stored at a register onto the stack.
|
|
|
|
MOV stores any set of bytes on the stack (which is a datum in
|
|
Little Endian) in the same order in the register i.e. a datum X
|
|
will be ordered the exact same way on the stack as in a register.
|
|
|
|
So instead of getting the value from a register, converting it to
|
|
host order, then pushing the datum (which converts it back to
|
|
little endian), let's just memcpy the value from the register to
|
|
the stack.
|
|
|
|
Note this means that we check for stack overflow here.
|
|
*/
|
|
#define VM_PUSH_REGISTER_CONSTR(TYPE, TYPE_CAP) \
|
|
err_t vm_push_##TYPE##_register(vm_t *vm, word_t reg) \
|
|
{ \
|
|
if (reg > (vm->registers.size / TYPE_CAP##_SIZE)) \
|
|
return ERR_INVALID_REGISTER_##TYPE_CAP; \
|
|
else if (vm->stack.ptr + TYPE_CAP##_SIZE >= vm->stack.max) \
|
|
return ERR_STACK_OVERFLOW; \
|
|
memcpy(vm->stack.data + vm->stack.ptr, \
|
|
vm->registers.bytes + (reg * TYPE_CAP##_SIZE), TYPE_CAP##_SIZE); \
|
|
vm->stack.ptr += TYPE_CAP##_SIZE; \
|
|
return ERR_OK; \
|
|
}
|
|
|
|
VM_PUSH_REGISTER_CONSTR(byte, BYTE)
|
|
VM_PUSH_REGISTER_CONSTR(short, SHORT)
|
|
VM_PUSH_REGISTER_CONSTR(hword, HWORD)
|
|
VM_PUSH_REGISTER_CONSTR(word, WORD)
|
|
|
|
/* Move a value from the stack into a specific register.
|
|
|
|
Values are stored in LE order on the stack. Values in registers
|
|
should be in LE order as well for consistency. Which means if, for
|
|
a value of N bytes, the array stack[top - N:top] is copied into the
|
|
register directly, we're done.
|
|
*/
|
|
#define VM_MOV_CONSTR(TYPE, TYPE_CAP) \
|
|
err_t vm_mov_##TYPE(vm_t *vm, word_t reg) \
|
|
{ \
|
|
if (reg >= (vm->registers.size / TYPE_CAP##_SIZE)) \
|
|
return ERR_INVALID_REGISTER_##TYPE_CAP; \
|
|
else if (vm->stack.ptr + TYPE_CAP##_SIZE >= vm->stack.max) \
|
|
return ERR_STACK_OVERFLOW; \
|
|
memcpy(vm->registers.bytes + (reg * TYPE_CAP##_SIZE), \
|
|
vm->stack.data + vm->stack.ptr - (TYPE_CAP##_SIZE), \
|
|
TYPE_CAP##_SIZE); \
|
|
vm->stack.ptr -= TYPE_CAP##_SIZE; \
|
|
return ERR_OK; \
|
|
}
|
|
|
|
VM_MOV_CONSTR(byte, BYTE)
|
|
VM_MOV_CONSTR(short, SHORT)
|
|
VM_MOV_CONSTR(hword, HWORD)
|
|
VM_MOV_CONSTR(word, WORD)
|
|
|
|
err_t vm_dup_byte(vm_t *vm, word_t w)
|
|
{
|
|
if (vm->stack.ptr < w + 1)
|
|
return ERR_STACK_UNDERFLOW;
|
|
return vm_push_byte(vm, DBYTE(vm->stack.data[vm->stack.ptr - 1 - w]));
|
|
}
|
|
|
|
#define VM_DUP_CONSTR(TYPE, TYPE_CAP) \
|
|
err_t vm_dup_##TYPE(vm_t *vm, word_t w) \
|
|
{ \
|
|
if (vm->stack.ptr < TYPE_CAP##_SIZE * (w + 1)) \
|
|
return ERR_STACK_UNDERFLOW; \
|
|
else if (vm->stack.ptr + TYPE_CAP##_SIZE >= vm->stack.max) \
|
|
return ERR_STACK_OVERFLOW; \
|
|
memcpy(vm->stack.data + vm->stack.ptr, \
|
|
vm->stack.data + vm->stack.ptr - (TYPE_CAP##_SIZE * (w + 1)), \
|
|
TYPE_CAP##_SIZE); \
|
|
vm->stack.ptr += TYPE_CAP##_SIZE; \
|
|
return ERR_OK; \
|
|
}
|
|
|
|
VM_DUP_CONSTR(short, SHORT)
|
|
VM_DUP_CONSTR(hword, HWORD)
|
|
VM_DUP_CONSTR(word, WORD)
|
|
|
|
#define VM_MALLOC_CONSTR(TYPE, TYPE_CAP) \
|
|
err_t vm_malloc_##TYPE(vm_t *vm) \
|
|
{ \
|
|
data_t n = {0}; \
|
|
err_t err = vm_pop_word(vm, &n); \
|
|
if (err) \
|
|
return err; \
|
|
page_t *page = heap_allocate(&vm->heap, n.as_word * TYPE_CAP##_SIZE); \
|
|
return vm_push_word(vm, DWORD((word_t)page)); \
|
|
}
|
|
|
|
VM_MALLOC_CONSTR(byte, BYTE)
|
|
VM_MALLOC_CONSTR(short, SHORT)
|
|
VM_MALLOC_CONSTR(hword, HWORD)
|
|
VM_MALLOC_CONSTR(word, WORD)
|
|
|
|
#define VM_MSET_CONSTR(TYPE, TYPE_CAP) \
|
|
err_t vm_mset_##TYPE(vm_t *vm) \
|
|
{ \
|
|
data_t object = {0}; \
|
|
err_t err = vm_pop_##TYPE(vm, &object); \
|
|
if (err) \
|
|
return err; \
|
|
data_t n = {0}; \
|
|
err = vm_pop_word(vm, &n); \
|
|
if (err) \
|
|
return err; \
|
|
data_t ptr = {0}; \
|
|
err = vm_pop_word(vm, &ptr); \
|
|
if (err) \
|
|
return err; \
|
|
page_t *page = (page_t *)ptr.as_word; \
|
|
if (n.as_word >= (page->available / TYPE_CAP##_SIZE)) \
|
|
return ERR_OUT_OF_BOUNDS; \
|
|
DARR_AT(TYPE##_t, page->data, n.as_word) = object.as_##TYPE; \
|
|
return ERR_OK; \
|
|
}
|
|
|
|
VM_MSET_CONSTR(byte, BYTE)
|
|
VM_MSET_CONSTR(short, SHORT)
|
|
VM_MSET_CONSTR(hword, HWORD)
|
|
VM_MSET_CONSTR(word, WORD)
|
|
|
|
#define VM_MGET_CONSTR(TYPE, TYPE_CAP) \
|
|
err_t vm_mget_##TYPE(vm_t *vm) \
|
|
{ \
|
|
data_t n = {0}; \
|
|
err_t err = vm_pop_word(vm, &n); \
|
|
if (err) \
|
|
return (err); \
|
|
data_t ptr = {0}; \
|
|
err = vm_pop_word(vm, &ptr); \
|
|
if (err) \
|
|
return err; \
|
|
page_t *page = (page_t *)ptr.as_word; \
|
|
if (n.as_word >= (page->available / TYPE_CAP##_SIZE)) \
|
|
return ERR_OUT_OF_BOUNDS; \
|
|
else if (vm->stack.ptr + TYPE_CAP##_SIZE >= vm->stack.max) \
|
|
return ERR_STACK_OVERFLOW; \
|
|
memcpy(vm->stack.data + vm->stack.ptr, \
|
|
page->data + (n.as_word * (TYPE_CAP##_SIZE)), TYPE_CAP##_SIZE); \
|
|
vm->stack.ptr += TYPE_CAP##_SIZE; \
|
|
return ERR_OK; \
|
|
}
|
|
|
|
VM_MGET_CONSTR(byte, BYTE)
|
|
VM_MGET_CONSTR(short, SHORT)
|
|
VM_MGET_CONSTR(hword, HWORD)
|
|
VM_MGET_CONSTR(word, WORD)
|
|
|
|
err_t vm_mdelete(vm_t *vm)
|
|
{
|
|
data_t ptr = {0};
|
|
err_t err = vm_pop_word(vm, &ptr);
|
|
if (err)
|
|
return err;
|
|
page_t *page = (page_t *)ptr.as_word;
|
|
bool done = heap_free(&vm->heap, page);
|
|
if (!done)
|
|
return ERR_INVALID_PAGE_ADDRESS;
|
|
return ERR_OK;
|
|
}
|
|
|
|
err_t vm_msize(vm_t *vm)
|
|
{
|
|
data_t ptr = {0};
|
|
err_t err = vm_pop_word(vm, &ptr);
|
|
if (err)
|
|
return err;
|
|
page_t *page = (page_t *)ptr.as_word;
|
|
return vm_push_word(vm, DWORD(page->available));
|
|
}
|
|
|
|
// TODO: rename this to something more appropriate
|
|
#define VM_NOT_TYPE(TYPEL, TYPEU) \
|
|
err_t vm_not_##TYPEL(vm_t *vm) \
|
|
{ \
|
|
data_t a = {0}; \
|
|
err_t err = vm_pop_##TYPEL(vm, &a); \
|
|
if (err) \
|
|
return err; \
|
|
return vm_push_##TYPEL(vm, D##TYPEU(!a.as_##TYPEL)); \
|
|
}
|
|
|
|
VM_NOT_TYPE(byte, BYTE)
|
|
VM_NOT_TYPE(short, SHORT)
|
|
VM_NOT_TYPE(hword, HWORD)
|
|
VM_NOT_TYPE(word, WORD)
|
|
|
|
// TODO: rename this to something more appropriate
|
|
#define VM_SAME_TYPE(COMPNAME, COMP, TYPEL, TYPEU) \
|
|
err_t vm_##COMPNAME##_##TYPEL(vm_t *vm) \
|
|
{ \
|
|
data_t a = {0}, b = {0}; \
|
|
err_t err = vm_pop_##TYPEL(vm, &a); \
|
|
if (err) \
|
|
return err; \
|
|
err = vm_pop_##TYPEL(vm, &b); \
|
|
if (err) \
|
|
return err; \
|
|
return vm_push_##TYPEL(vm, D##TYPEU(a.as_##TYPEL COMP b.as_##TYPEL)); \
|
|
}
|
|
|
|
// TODO: rename this to something more appropriate
|
|
#define VM_COMPARATOR_TYPE(COMPNAME, COMP, TYPEL, GETL) \
|
|
err_t vm_##COMPNAME##_##GETL(vm_t *vm) \
|
|
{ \
|
|
data_t a = {0}, b = {0}; \
|
|
err_t err = vm_pop_##TYPEL(vm, &a); \
|
|
if (err) \
|
|
return err; \
|
|
err = vm_pop_##TYPEL(vm, &b); \
|
|
if (err) \
|
|
return err; \
|
|
return vm_push_byte(vm, DBYTE(b.as_##GETL COMP a.as_##GETL)); \
|
|
}
|
|
|
|
VM_SAME_TYPE(or, |, byte, BYTE)
|
|
VM_SAME_TYPE(or, |, short, SHORT)
|
|
VM_SAME_TYPE(or, |, hword, HWORD)
|
|
VM_SAME_TYPE(or, |, word, WORD)
|
|
|
|
VM_SAME_TYPE(and, &, byte, BYTE)
|
|
VM_SAME_TYPE(and, &, short, SHORT)
|
|
VM_SAME_TYPE(and, &, hword, HWORD)
|
|
VM_SAME_TYPE(and, &, word, WORD)
|
|
|
|
VM_SAME_TYPE(xor, ^, byte, BYTE)
|
|
VM_SAME_TYPE(xor, ^, short, SHORT)
|
|
VM_SAME_TYPE(xor, ^, hword, HWORD)
|
|
VM_SAME_TYPE(xor, ^, word, WORD)
|
|
|
|
VM_SAME_TYPE(plus, +, byte, BYTE)
|
|
VM_SAME_TYPE(plus, +, short, SHORT)
|
|
VM_SAME_TYPE(plus, +, hword, HWORD)
|
|
VM_SAME_TYPE(plus, +, word, WORD)
|
|
|
|
VM_SAME_TYPE(sub, -, byte, BYTE)
|
|
VM_SAME_TYPE(sub, -, short, SHORT)
|
|
VM_SAME_TYPE(sub, -, hword, HWORD)
|
|
VM_SAME_TYPE(sub, -, word, WORD)
|
|
|
|
VM_SAME_TYPE(mult, *, byte, BYTE)
|
|
VM_SAME_TYPE(mult, *, short, SHORT)
|
|
VM_SAME_TYPE(mult, *, hword, HWORD)
|
|
VM_SAME_TYPE(mult, *, word, WORD)
|
|
|
|
VM_COMPARATOR_TYPE(eq, ==, byte, byte)
|
|
VM_COMPARATOR_TYPE(eq, ==, byte, sbyte)
|
|
VM_COMPARATOR_TYPE(eq, ==, short, short)
|
|
VM_COMPARATOR_TYPE(eq, ==, short, sshort)
|
|
VM_COMPARATOR_TYPE(eq, ==, hword, hword)
|
|
VM_COMPARATOR_TYPE(eq, ==, hword, shword)
|
|
VM_COMPARATOR_TYPE(eq, ==, word, word)
|
|
VM_COMPARATOR_TYPE(eq, ==, word, sword)
|
|
|
|
VM_COMPARATOR_TYPE(lt, <, byte, byte)
|
|
VM_COMPARATOR_TYPE(lt, <, byte, sbyte)
|
|
VM_COMPARATOR_TYPE(lt, <, short, short)
|
|
VM_COMPARATOR_TYPE(lt, <, short, sshort)
|
|
VM_COMPARATOR_TYPE(lt, <, hword, hword)
|
|
VM_COMPARATOR_TYPE(lt, <, hword, shword)
|
|
VM_COMPARATOR_TYPE(lt, <, word, word)
|
|
VM_COMPARATOR_TYPE(lt, <, word, sword)
|
|
|
|
VM_COMPARATOR_TYPE(lte, <=, byte, byte)
|
|
VM_COMPARATOR_TYPE(lte, <=, byte, sbyte)
|
|
VM_COMPARATOR_TYPE(lte, <=, short, short)
|
|
VM_COMPARATOR_TYPE(lte, <=, short, sshort)
|
|
VM_COMPARATOR_TYPE(lte, <=, hword, hword)
|
|
VM_COMPARATOR_TYPE(lte, <=, hword, shword)
|
|
VM_COMPARATOR_TYPE(lte, <=, word, word)
|
|
VM_COMPARATOR_TYPE(lte, <=, word, sword)
|
|
|
|
VM_COMPARATOR_TYPE(gt, >, byte, byte)
|
|
VM_COMPARATOR_TYPE(gt, >, byte, sbyte)
|
|
VM_COMPARATOR_TYPE(gt, >, short, short)
|
|
VM_COMPARATOR_TYPE(gt, >, short, sshort)
|
|
VM_COMPARATOR_TYPE(gt, >, hword, hword)
|
|
VM_COMPARATOR_TYPE(gt, >, hword, shword)
|
|
VM_COMPARATOR_TYPE(gt, >, word, word)
|
|
VM_COMPARATOR_TYPE(gt, >, word, sword)
|
|
|
|
VM_COMPARATOR_TYPE(gte, >=, byte, byte)
|
|
VM_COMPARATOR_TYPE(gte, >=, byte, sbyte)
|
|
VM_COMPARATOR_TYPE(gte, >=, short, short)
|
|
VM_COMPARATOR_TYPE(gte, >=, short, sshort)
|
|
VM_COMPARATOR_TYPE(gte, >=, hword, hword)
|
|
VM_COMPARATOR_TYPE(gte, >=, hword, shword)
|
|
VM_COMPARATOR_TYPE(gte, >=, word, word)
|
|
VM_COMPARATOR_TYPE(gte, >=, word, sword)
|