Newer
Older
Tardis / lang / bytecode.c
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 John Watts and the LuminaSensum contributors

#include "bytecode.h"
#include "boolean.h"
#include "error.h"
#include "number.h"
#include "object.h"
#include "string.h"
#include "vm.h"

// Opcode definitions, see bytecode_run for further documentation
enum opcodes {
	OP_END = 0x00,
	OP_NUM = 0x01,
	OP_RET = 0x03,
	// 0x02 was an earlier variant of OP_DROP
	OP_CALL = 0x04,
	OP_NULL = 0x05,
	OP_GET = 0x06,
	OP_SET = 0x07,
	OP_DROP = 0x08,
	OP_DEPTH_CHECK = 0x09,
	OP_SELF = 0x0A,
	OP_BOOLEAN = 0x0B,
};

void bytecode_run(VmState state, Object obj, const unsigned char *bytecode) {
	const unsigned char *pos_code = &bytecode[0];
	unsigned char op = OP_END;
	while ((op = *pos_code++) != OP_END) {
		switch (op) {
		// OP_NUM pushes a number on to the stack
		// First argument is a 4-byte little endian number
		case OP_NUM: {
			int num = 0;
			num |= *pos_code++ << 0;
			num |= *pos_code++ << 8;
			num |= *pos_code++ << 16;
			num |= *pos_code++ << 24;
			vm_stack_push(state, number_create(num));
			break;
		}
		// OP_BOOLEAN pushes a boolean on to the stack
		// First argument is a 1-byte boolean
		case OP_BOOLEAN: {
			int value = *pos_code++;
			abort_if(value != 0 && value != 1, "invalid boolean");
			vm_stack_push(state, boolean_create(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);
			vm_call(state, obj, dispatch, arg_count);
			object_drop(&obj);
			break;
		}
		// OP_NULL pushes object_none on to the stack
		case OP_NULL: {
			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: {
			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: {
			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: {
			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++;
			abort_if(vm_stack_depth(state) != depth,
				"stack depth mismatch");
			break;
		}
		// OP_SELF pushes obj on to the stack
		case OP_SELF: {
			object_hold(obj);
			vm_stack_push(state, obj);
			break;
		}
		// OP_RET returns
		case OP_RET: {
			return;
		}
		// Unknown op, error out
		default:
			abort_msg("bytecode_run: Unknown op");
		}
	}
	// Uh-oh, no more bytecode?
	abort_msg("bytecode_run: Ran out of bytecode to run!");
}