625 lines
15 KiB
C++
625 lines
15 KiB
C++
/**************************************************************************/
|
|
/* aethex_vm.cpp */
|
|
/**************************************************************************/
|
|
/* This file is part of: */
|
|
/* AETHEX ENGINE */
|
|
/* https://aethex.foundation */
|
|
/**************************************************************************/
|
|
/* Copyright (c) 2026-present AeThex Labs. */
|
|
/**************************************************************************/
|
|
|
|
#include "aethex_vm.h"
|
|
#include "core/io/file_access.h"
|
|
#include "core/os/os.h"
|
|
|
|
AeThexVM::AeThexVM() {
|
|
reset_stack();
|
|
register_builtin_functions();
|
|
}
|
|
|
|
AeThexVM::~AeThexVM() {
|
|
}
|
|
|
|
void AeThexVM::_bind_methods() {
|
|
ClassDB::bind_method(D_METHOD("set_global", "name", "value"), &AeThexVM::set_global);
|
|
ClassDB::bind_method(D_METHOD("get_global", "name"), &AeThexVM::get_global);
|
|
ClassDB::bind_method(D_METHOD("has_global", "name"), &AeThexVM::has_global);
|
|
ClassDB::bind_method(D_METHOD("get_error"), &AeThexVM::get_error);
|
|
ClassDB::bind_method(D_METHOD("has_error"), &AeThexVM::has_error);
|
|
ClassDB::bind_method(D_METHOD("set_debug_trace", "trace"), &AeThexVM::set_debug_trace);
|
|
|
|
BIND_ENUM_CONSTANT(INTERPRET_OK);
|
|
BIND_ENUM_CONSTANT(INTERPRET_COMPILE_ERROR);
|
|
BIND_ENUM_CONSTANT(INTERPRET_RUNTIME_ERROR);
|
|
}
|
|
|
|
void AeThexVM::register_builtin_functions() {
|
|
// Register standard math constants and basic functions via globals
|
|
globals["PI"] = Math::PI;
|
|
globals["TAU"] = Math::TAU;
|
|
globals["INF"] = INFINITY;
|
|
globals["E"] = Math::E;
|
|
|
|
// Built-in types
|
|
globals["String"] = Variant();
|
|
globals["Array"] = Variant();
|
|
globals["Dictionary"] = Variant();
|
|
globals["Vector2"] = Variant();
|
|
globals["Vector3"] = Variant();
|
|
|
|
// Note: Native math functions like abs, floor, ceil, etc. are provided
|
|
// via the AeThex script library or through Godot's expression evaluator
|
|
}
|
|
|
|
void AeThexVM::reset_stack() {
|
|
stack_top = 0;
|
|
frame_count = 0;
|
|
runtime_error = "";
|
|
}
|
|
|
|
void AeThexVM::push(const Variant &value) {
|
|
if (stack_top >= STACK_MAX) {
|
|
runtime_error_at("Stack overflow");
|
|
return;
|
|
}
|
|
stack[stack_top++] = value;
|
|
}
|
|
|
|
Variant AeThexVM::pop() {
|
|
if (stack_top <= 0) {
|
|
runtime_error_at("Stack underflow");
|
|
return Variant();
|
|
}
|
|
return stack[--stack_top];
|
|
}
|
|
|
|
Variant AeThexVM::peek(int distance) const {
|
|
if (stack_top - 1 - distance < 0) {
|
|
return Variant();
|
|
}
|
|
return stack[stack_top - 1 - distance];
|
|
}
|
|
|
|
void AeThexVM::set_global(const String &name, const Variant &value) {
|
|
globals[name] = value;
|
|
}
|
|
|
|
Variant AeThexVM::get_global(const String &name) const {
|
|
if (globals.has(name)) {
|
|
return globals[name];
|
|
}
|
|
return Variant();
|
|
}
|
|
|
|
bool AeThexVM::has_global(const String &name) const {
|
|
return globals.has(name);
|
|
}
|
|
|
|
void AeThexVM::register_native_function(const String &name, const Callable &callable) {
|
|
native_functions[name] = callable;
|
|
}
|
|
|
|
AeThexVM::InterpretResult AeThexVM::execute(const AeThexCompiler::CompiledScript *p_script) {
|
|
if (!p_script) {
|
|
runtime_error = "No script to execute";
|
|
return INTERPRET_COMPILE_ERROR;
|
|
}
|
|
|
|
script = p_script;
|
|
reset_stack();
|
|
|
|
// Set up main frame
|
|
frames[0].chunk = &script->main_chunk;
|
|
frames[0].ip = 0;
|
|
frames[0].stack_base = 0;
|
|
frames[0].function_name = "main";
|
|
frame_count = 1;
|
|
|
|
return run();
|
|
}
|
|
|
|
AeThexVM::InterpretResult AeThexVM::call(const String &function_name, const Vector<Variant> &args) {
|
|
if (!script) {
|
|
runtime_error = "No script loaded";
|
|
return INTERPRET_COMPILE_ERROR;
|
|
}
|
|
|
|
if (!script->functions.has(function_name)) {
|
|
runtime_error = "Function not found: " + function_name;
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
|
|
// Push arguments
|
|
for (const Variant &arg : args) {
|
|
push(arg);
|
|
}
|
|
|
|
// Set up call frame
|
|
CallFrame *frame = &frames[frame_count++];
|
|
frame->chunk = &script->functions[function_name];
|
|
frame->ip = 0;
|
|
frame->stack_base = stack_top - args.size();
|
|
frame->function_name = function_name;
|
|
|
|
return run();
|
|
}
|
|
|
|
uint8_t AeThexVM::read_byte() {
|
|
CallFrame *frame = &frames[frame_count - 1];
|
|
return frame->chunk->code[frame->ip++];
|
|
}
|
|
|
|
uint16_t AeThexVM::read_short() {
|
|
CallFrame *frame = &frames[frame_count - 1];
|
|
uint16_t value = frame->chunk->code[frame->ip] |
|
|
(frame->chunk->code[frame->ip + 1] << 8);
|
|
frame->ip += 2;
|
|
return value;
|
|
}
|
|
|
|
int32_t AeThexVM::read_int() {
|
|
CallFrame *frame = &frames[frame_count - 1];
|
|
int32_t value = frame->chunk->code[frame->ip] |
|
|
(frame->chunk->code[frame->ip + 1] << 8) |
|
|
(frame->chunk->code[frame->ip + 2] << 16) |
|
|
(frame->chunk->code[frame->ip + 3] << 24);
|
|
frame->ip += 4;
|
|
return value;
|
|
}
|
|
|
|
float AeThexVM::read_float() {
|
|
union { float f; int32_t i; } u;
|
|
u.i = read_int();
|
|
return u.f;
|
|
}
|
|
|
|
const String &AeThexVM::read_string() {
|
|
uint8_t idx = read_byte();
|
|
CallFrame *frame = &frames[frame_count - 1];
|
|
return frame->chunk->strings[idx];
|
|
}
|
|
|
|
Variant AeThexVM::read_constant() {
|
|
uint8_t idx = read_byte();
|
|
CallFrame *frame = &frames[frame_count - 1];
|
|
return frame->chunk->constants[idx];
|
|
}
|
|
|
|
bool AeThexVM::call_function(const String &name, int arg_count) {
|
|
if (script->functions.has(name)) {
|
|
if (frame_count >= FRAMES_MAX) {
|
|
runtime_error_at("Stack overflow (call frames)");
|
|
return false;
|
|
}
|
|
|
|
CallFrame *frame = &frames[frame_count++];
|
|
frame->chunk = &script->functions[name];
|
|
frame->ip = 0;
|
|
frame->stack_base = stack_top - arg_count;
|
|
frame->function_name = name;
|
|
return true;
|
|
}
|
|
|
|
return call_native(name, arg_count);
|
|
}
|
|
|
|
bool AeThexVM::call_native(const String &name, int arg_count) {
|
|
if (native_functions.has(name)) {
|
|
// Collect arguments
|
|
Array args;
|
|
for (int i = arg_count - 1; i >= 0; i--) {
|
|
args.push_front(peek(i));
|
|
}
|
|
|
|
// Pop arguments
|
|
for (int i = 0; i < arg_count; i++) {
|
|
pop();
|
|
}
|
|
|
|
// Call native function
|
|
Variant result;
|
|
Callable::CallError error;
|
|
const Variant *argv[16];
|
|
for (int i = 0; i < MIN(arg_count, 16); i++) {
|
|
argv[i] = &args[i];
|
|
}
|
|
|
|
native_functions[name].callp(argv, arg_count, result, error);
|
|
|
|
if (error.error != Callable::CallError::CALL_OK) {
|
|
runtime_error_at("Native function error: " + name);
|
|
return false;
|
|
}
|
|
|
|
push(result);
|
|
return true;
|
|
}
|
|
|
|
runtime_error_at("Undefined function: " + name);
|
|
return false;
|
|
}
|
|
|
|
void AeThexVM::runtime_error_at(const String &message) {
|
|
CallFrame *frame = &frames[frame_count - 1];
|
|
runtime_error = message + " at " + frame->function_name + ":" + itos(frame->ip);
|
|
}
|
|
|
|
AeThexVM::InterpretResult AeThexVM::run() {
|
|
CallFrame *frame = &frames[frame_count - 1];
|
|
|
|
#define READ_BYTE() (frame->chunk->code[frame->ip++])
|
|
#define READ_SHORT() (frame->ip += 2, (uint16_t)(frame->chunk->code[frame->ip - 2] | (frame->chunk->code[frame->ip - 1] << 8)))
|
|
#define BINARY_OP(op) \
|
|
do { \
|
|
Variant b = pop(); \
|
|
Variant a = pop(); \
|
|
push(Variant::evaluate(Variant::op, a, b)); \
|
|
} while (false)
|
|
|
|
while (true) {
|
|
if (frame->ip >= frame->chunk->code.size()) {
|
|
break;
|
|
}
|
|
|
|
if (debug_trace) {
|
|
print_stack();
|
|
}
|
|
|
|
uint8_t instruction = READ_BYTE();
|
|
|
|
switch (instruction) {
|
|
case AeThexCompiler::OP_PUSH_NULL:
|
|
push(Variant());
|
|
break;
|
|
|
|
case AeThexCompiler::OP_PUSH_BOOL:
|
|
push(READ_BYTE() != 0);
|
|
break;
|
|
|
|
case AeThexCompiler::OP_PUSH_INT: {
|
|
int32_t value = frame->chunk->code[frame->ip] |
|
|
(frame->chunk->code[frame->ip + 1] << 8) |
|
|
(frame->chunk->code[frame->ip + 2] << 16) |
|
|
(frame->chunk->code[frame->ip + 3] << 24);
|
|
frame->ip += 4;
|
|
push(value);
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_PUSH_FLOAT: {
|
|
union { float f; int32_t i; } u;
|
|
u.i = frame->chunk->code[frame->ip] |
|
|
(frame->chunk->code[frame->ip + 1] << 8) |
|
|
(frame->chunk->code[frame->ip + 2] << 16) |
|
|
(frame->chunk->code[frame->ip + 3] << 24);
|
|
frame->ip += 4;
|
|
push(u.f);
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_PUSH_STRING: {
|
|
uint8_t idx = READ_BYTE();
|
|
if (idx < frame->chunk->strings.size()) {
|
|
push(frame->chunk->strings[idx]);
|
|
} else {
|
|
push("");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_POP:
|
|
pop();
|
|
break;
|
|
|
|
case AeThexCompiler::OP_DUP:
|
|
push(peek());
|
|
break;
|
|
|
|
case AeThexCompiler::OP_LOAD_LOCAL: {
|
|
uint8_t slot = READ_BYTE();
|
|
push(stack[frame->stack_base + slot]);
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_STORE_LOCAL: {
|
|
uint8_t slot = READ_BYTE();
|
|
stack[frame->stack_base + slot] = peek();
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_LOAD_GLOBAL: {
|
|
uint8_t idx = READ_BYTE();
|
|
String name = frame->chunk->strings[idx];
|
|
if (globals.has(name)) {
|
|
push(globals[name]);
|
|
} else {
|
|
push(Variant());
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_STORE_GLOBAL: {
|
|
uint8_t idx = READ_BYTE();
|
|
String name = frame->chunk->strings[idx];
|
|
globals[name] = peek();
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_ADD:
|
|
BINARY_OP(OP_ADD);
|
|
break;
|
|
|
|
case AeThexCompiler::OP_SUB:
|
|
BINARY_OP(OP_SUBTRACT);
|
|
break;
|
|
|
|
case AeThexCompiler::OP_MUL:
|
|
BINARY_OP(OP_MULTIPLY);
|
|
break;
|
|
|
|
case AeThexCompiler::OP_DIV:
|
|
BINARY_OP(OP_DIVIDE);
|
|
break;
|
|
|
|
case AeThexCompiler::OP_MOD:
|
|
BINARY_OP(OP_MODULE);
|
|
break;
|
|
|
|
case AeThexCompiler::OP_NEG: {
|
|
Variant a = pop();
|
|
push(Variant::evaluate(Variant::OP_NEGATE, a, Variant()));
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_EQ:
|
|
BINARY_OP(OP_EQUAL);
|
|
break;
|
|
|
|
case AeThexCompiler::OP_NE:
|
|
BINARY_OP(OP_NOT_EQUAL);
|
|
break;
|
|
|
|
case AeThexCompiler::OP_LT:
|
|
BINARY_OP(OP_LESS);
|
|
break;
|
|
|
|
case AeThexCompiler::OP_LE:
|
|
BINARY_OP(OP_LESS_EQUAL);
|
|
break;
|
|
|
|
case AeThexCompiler::OP_GT:
|
|
BINARY_OP(OP_GREATER);
|
|
break;
|
|
|
|
case AeThexCompiler::OP_GE:
|
|
BINARY_OP(OP_GREATER_EQUAL);
|
|
break;
|
|
|
|
case AeThexCompiler::OP_NOT: {
|
|
Variant a = pop();
|
|
push(Variant::evaluate(Variant::OP_NOT, a, Variant()));
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_AND:
|
|
BINARY_OP(OP_AND);
|
|
break;
|
|
|
|
case AeThexCompiler::OP_OR:
|
|
BINARY_OP(OP_OR);
|
|
break;
|
|
|
|
case AeThexCompiler::OP_JUMP: {
|
|
uint16_t offset = READ_SHORT();
|
|
frame->ip += offset;
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_JUMP_IF: {
|
|
uint16_t offset = READ_SHORT();
|
|
if (peek().booleanize()) {
|
|
frame->ip += offset;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_JUMP_IF_NOT: {
|
|
uint16_t offset = READ_SHORT();
|
|
if (!peek().booleanize()) {
|
|
frame->ip += offset;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_LOOP: {
|
|
uint16_t offset = READ_SHORT();
|
|
frame->ip -= offset;
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_CALL: {
|
|
uint8_t arg_count = READ_BYTE();
|
|
Variant callee = peek(arg_count);
|
|
|
|
if (callee.get_type() == Variant::STRING) {
|
|
if (!call_function(callee, arg_count)) {
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
frame = &frames[frame_count - 1];
|
|
} else if (callee.get_type() == Variant::CALLABLE) {
|
|
// Direct callable
|
|
Array args;
|
|
for (int i = arg_count - 1; i >= 0; i--) {
|
|
args.push_front(peek(i));
|
|
}
|
|
for (int i = 0; i < arg_count + 1; i++) {
|
|
pop();
|
|
}
|
|
|
|
Variant result = callee.call("call_funcv", args);
|
|
push(result);
|
|
} else {
|
|
runtime_error_at("Can only call functions");
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_RETURN: {
|
|
Variant result = pop();
|
|
|
|
// Pop locals
|
|
stack_top = frame->stack_base;
|
|
|
|
frame_count--;
|
|
if (frame_count == 0) {
|
|
push(result);
|
|
return INTERPRET_OK;
|
|
}
|
|
|
|
push(result);
|
|
frame = &frames[frame_count - 1];
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_NEW_ARRAY: {
|
|
uint8_t count = READ_BYTE();
|
|
Array arr;
|
|
for (int i = count - 1; i >= 0; i--) {
|
|
arr.push_front(peek(i));
|
|
}
|
|
for (int i = 0; i < count; i++) {
|
|
pop();
|
|
}
|
|
push(arr);
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_NEW_DICT: {
|
|
uint8_t count = READ_BYTE();
|
|
Dictionary dict;
|
|
for (int i = (count * 2) - 1; i >= 0; i -= 2) {
|
|
dict[peek(i)] = peek(i - 1);
|
|
}
|
|
for (int i = 0; i < count * 2; i++) {
|
|
pop();
|
|
}
|
|
push(dict);
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_INDEX_GET: {
|
|
Variant index = pop();
|
|
Variant container = pop();
|
|
bool valid = false;
|
|
Variant result = container.get(index, &valid);
|
|
if (!valid) {
|
|
runtime_error_at("Invalid index access");
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
push(result);
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_INDEX_SET: {
|
|
Variant value = pop();
|
|
Variant index = pop();
|
|
Variant container = pop();
|
|
bool valid = false;
|
|
container.set(index, value, &valid);
|
|
if (!valid) {
|
|
runtime_error_at("Invalid index assignment");
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
push(value);
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_SYNC: {
|
|
// Cross-platform sync operation
|
|
// In engine: just a barrier/wait point
|
|
Variant data = pop();
|
|
// TODO: Implement actual sync logic
|
|
push(data);
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_BEACON_EMIT: {
|
|
uint8_t idx = READ_BYTE();
|
|
String beacon_name = frame->chunk->strings[idx];
|
|
// TODO: Emit beacon signal
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_NOTIFY: {
|
|
Variant message = pop();
|
|
print_line("[AeThex] " + message.stringify());
|
|
break;
|
|
}
|
|
|
|
case AeThexCompiler::OP_AWAIT: {
|
|
// TODO: Implement async/await
|
|
break;
|
|
}
|
|
|
|
default:
|
|
runtime_error_at("Unknown opcode: " + itos(instruction));
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
}
|
|
|
|
#undef READ_BYTE
|
|
#undef READ_SHORT
|
|
#undef BINARY_OP
|
|
|
|
return INTERPRET_OK;
|
|
}
|
|
|
|
void AeThexVM::print_stack() const {
|
|
print_line(" Stack: ");
|
|
for (int i = 0; i < stack_top; i++) {
|
|
print_line(" [ " + stack[i].stringify() + " ]");
|
|
}
|
|
}
|
|
|
|
void AeThexVM::disassemble_chunk(const AeThexCompiler::Chunk &chunk, const String &name) const {
|
|
print_line("== " + name + " ==");
|
|
|
|
int offset = 0;
|
|
while (offset < chunk.code.size()) {
|
|
uint8_t instruction = chunk.code[offset];
|
|
String line = vformat("%04d ", offset);
|
|
line += AeThexCompiler::opcode_name((AeThexCompiler::Opcode)instruction);
|
|
|
|
switch (instruction) {
|
|
case AeThexCompiler::OP_PUSH_BOOL:
|
|
case AeThexCompiler::OP_LOAD_LOCAL:
|
|
case AeThexCompiler::OP_STORE_LOCAL:
|
|
case AeThexCompiler::OP_LOAD_GLOBAL:
|
|
case AeThexCompiler::OP_STORE_GLOBAL:
|
|
case AeThexCompiler::OP_PUSH_STRING:
|
|
case AeThexCompiler::OP_CALL:
|
|
offset++;
|
|
line += " " + itos(chunk.code[offset]);
|
|
break;
|
|
|
|
case AeThexCompiler::OP_PUSH_INT:
|
|
case AeThexCompiler::OP_PUSH_FLOAT:
|
|
offset += 4;
|
|
break;
|
|
|
|
case AeThexCompiler::OP_JUMP:
|
|
case AeThexCompiler::OP_JUMP_IF:
|
|
case AeThexCompiler::OP_JUMP_IF_NOT:
|
|
case AeThexCompiler::OP_LOOP:
|
|
offset += 2;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
print_line(line);
|
|
offset++;
|
|
}
|
|
}
|