// SPDX-License-Identifier: MIT // Copyright (c) 2024 John Watts and the LuminaSensum contributors #define DEBUG_MSG 0 // Set to 1 to debug bytecode #include "bytecode.h" #include "boolean.h" #include "debug.h" #include "list.h" #include "object.h" #include "rational.h" #include "string.h" #include "vm.h" #define MODULE_INTERNAL_API #include "module.h" // Opcode definitions, see bytecode_run for further documentation enum opcodes { OP_END = 0x00, OP_RATIO1 = 0x01, OP_RET = 0x03, OP_CREATE = 0x02, OP_CALL = 0x04, OP_NONE = 0x05, OP_GET = 0x06, OP_SET = 0x07, OP_DROP = 0x08, OP_DEPTH_CHECK = 0x09, OP_SELF = 0x0A, OP_BOOLEAN = 0x0B, OP_JUMP_FALSE = 0x0C, OP_JUMP_ALWAYS = 0x0D, OP_TAIL_CALL = 0x0E, OP_MOD_USE = 0x0F, OP_MOD_TEXT = 0x10, }; void bytecode_run(VmState state, Object obj, const unsigned char *bytecode, Object module) { const unsigned char *pos_code = &bytecode[0]; unsigned char op = OP_END; while ((op = *pos_code++) != OP_END) { switch (op) { // OP_RATIO1 pushes a rational on to the stack // First argument is a 4-byte little endian numerator // The denominator is always 1 case OP_RATIO1: { int num = 0; num |= *pos_code++ << 0; num |= *pos_code++ << 8; num |= *pos_code++ << 16; num |= *pos_code++ << 24; debug_msg("OP_RATIO1 %i\n", num); vm_stack_push(state, rational_create(state, num, 1)); break; } // OP_BOOLEAN pushes a boolean on to the stack // First argument is a 1-byte boolean case OP_BOOLEAN: { int value = *pos_code++; debug_msg("OP_BOOLEAN %i\n", value); vm_abort_if(state, value != 0 && value != 1, "invalid boolean"); vm_stack_push(state, boolean_create(state, value)); break; } // OP_CALL dispatches a call to the top of stack object // First argument is a 1-byte argument count // Second argument is the call string case OP_CALL: { int arg_count = *pos_code++; const char *dispatch = (const char *)pos_code; pos_code += strlen(dispatch) + 1; // Skip NULL too Object obj = vm_stack_pop(state); debug_msg("OP_CALL %s\n", dispatch); vm_call(state, obj, dispatch, arg_count); object_drop(state, &obj); break; } // OP_TAIL_CALL queues a call and returns // First argument is a 1-byte argument count // Second argument is the call string case OP_TAIL_CALL: { int arg_count = *pos_code++; const char *dispatch = (const char *)pos_code; pos_code += strlen(dispatch) + 1; // Skip NULL too Object obj = vm_stack_pop(state); int args_start = vm_stack_depth(state) - arg_count; for (int i = 0; i < arg_count; ++i) { int stack_pos = args_start + i; Object arg = vm_stack_get(state, stack_pos); vm_stack_set(state, i, arg); } int remaining = vm_stack_depth(state) - arg_count; if (remaining) vm_stack_drop(state, remaining); debug_msg("OP_TAIL_CALL %s\n", dispatch); vm_tailcall(state, obj, dispatch); object_drop(state, &obj); return; } // OP_NONE pushes object_none on to the stack case OP_NONE: { debug_msg("OP_NONE\n"); vm_stack_push(state, object_none()); break; } // OP_GET duplicates an element from the stack and stores // the result at the top // First argument is the stack index case OP_GET: { debug_msg("OP_GET %i\n", *pos_code); Object obj = vm_stack_get(state, *pos_code++); vm_stack_push(state, obj); break; } // OP_SET stores the stack top element at a stack index // The stack top element is dropped in the process // First argument is the stack index case OP_SET: { debug_msg("OP_SET %i\n", *pos_code); Object obj = vm_stack_pop(state); vm_stack_set(state, *pos_code++, obj); break; } // OP_DROP drops multiple stack elements starting at the top // First argument is the number of elements to drop case OP_DROP: { debug_msg("OP_DROP %i\n", *pos_code); vm_stack_drop(state, *pos_code++); break; } // OP_DEPTH_CHECK checks that the stack is a certain size // First argument is the expected depth case OP_DEPTH_CHECK: { int depth = *pos_code++; debug_msg("OP_DEPTH_CHECK %i\n", depth); vm_abort_if(state, vm_stack_depth(state) != depth, "stack depth mismatch"); break; } // OP_SELF pushes obj on to the stack case OP_SELF: { debug_msg("OP_SELF\n"); object_hold(state, obj); vm_stack_push(state, obj); break; } // OP_JUMP_FALSE jumps if the top of stack is false case OP_JUMP_FALSE: { int offset = *pos_code++; Object test = vm_stack_pop(state); bool test_result = boolean_value(state, test); debug_msg("OP_JUMP_SELF %i %i\n", offset, test_result); if (test_result == false) pos_code += offset; object_drop(state, &test); break; } // OP_JUMP_ALWAYS jumps case OP_JUMP_ALWAYS: { int offset = *pos_code++; debug_msg("OP_JUMP_ALWAYS %i\n", offset); pos_code += offset; break; } // OP_RET returns case OP_RET: { debug_msg("OP_RET\n"); return; } // OP_MOD_USE pushes an argument on the stack case OP_MOD_USE: { debug_msg("OP_MOD_USE %i\n", *pos_code); int index = *pos_code++; Object obj = module_runtime_use(state, module, index); vm_stack_push(state, obj); break; } // OP_MOD_TEXT pushes a text object on to the stack case OP_MOD_TEXT: { debug_msg("OP_MOD_TEXT %i\n", *pos_code); int index = *pos_code++; Object obj = module_runtime_text(state, module, index); vm_stack_push(state, obj); break; } // OP_CREATE creates a class from a module scope // First argument is the number in the scope's classes case OP_CREATE: { debug_msg("OP_CREATE %i\n", *pos_code); int index = *pos_code++; // Create a new module refernce for the class object_hold(state, module); Object obj = module_class_create(state, module, index); vm_stack_push(state, obj); break; } // Unknown op, error out default: { debug_msg("Unknown OP 0x%02x\n", op); vm_abort_msg(state, "bytecode_run: Unknown op"); } } } // Uh-oh, no more bytecode? vm_abort_msg(state, "bytecode_run: Ran out of bytecode to run!"); }