/**************************************************************************/ /* 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 &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++; } }