Skip to content

Commit

Permalink
[#74] feat(vm): add support for naked function overrides
Browse files Browse the repository at this point in the history
This enables handling of loop constructs in mod frameworks like Ikarus/LeGo as well as some initial support for a debuggable VM.
  • Loading branch information
Try committed Jul 30, 2023
1 parent bcb47c1 commit e481bca
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 11 deletions.
20 changes: 15 additions & 5 deletions include/phoenix/script.hh
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ namespace phoenix {

/// \brief Flags of symbols.
namespace symbol_flag {
static constexpr auto const_ = 1U << 0U; ///< The symbol is not mutable.
static constexpr auto return_ = 1U << 1U; ///< The symbol is a function and has a return value.
static constexpr auto member = 1U << 2U; ///< The symbol is a class member.
static constexpr auto external = 1U << 3U; ///< The symbol refers to an external function.
static constexpr auto merged = 1U << 4U; ///< Unused.
static constexpr auto const_ = 1U << 0U; ///< The symbol is not mutable.
static constexpr auto return_ = 1U << 1U; ///< The symbol is a function and has a return value.
static constexpr auto member = 1U << 2U; ///< The symbol is a class member.
static constexpr auto external = 1U << 3U; ///< The symbol refers to an external function.
static constexpr auto merged = 1U << 4U; ///< Unused.
static constexpr auto access_trap = 1U << 6U; ///< VM should call trap callback, when symbol accessed.
} // namespace symbol_flag

/// \brief All opcodes supported by the daedalus interpreter.
Expand Down Expand Up @@ -447,6 +448,10 @@ namespace phoenix {
this->get_instance()->_m_type == &typeid(T);
}

/// \brief Allows VM traps on access to this symbol
/// \param enable true to enable and false to disable
PHOENIX_API void set_access_trap_enable(bool enable) noexcept;

/// \brief Tests whether the symbol is a constant.
/// \return `true` if the symbol is a constant, `false` if not.
[[nodiscard]] PHOENIX_API inline bool is_const() const noexcept {
Expand All @@ -472,6 +477,11 @@ namespace phoenix {
return (_m_flags & symbol_flag::merged) != 0;
}

/// \brief Tests whether the symbol has access trap.
/// \return `true` if the symbol has trap enabled, `false` if not.
[[nodiscard]] PHOENIX_API inline bool has_access_trap() const noexcept {
return (_m_flags & symbol_flag::access_trap) != 0;
}
/// \brief brief Tests whether the symbol is a compiler-generated symbol
/// \return return `true` if the symbol is generated, `false` if not.
[[nodiscard]] PHOENIX_API inline bool is_generated() const noexcept {
Expand Down
32 changes: 31 additions & 1 deletion include/phoenix/vm.hh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
namespace phoenix {
struct _ignore_return_value {};

struct naked_call {};

template <typename T>
static constexpr bool is_instance_ptr_v = false;

Expand Down Expand Up @@ -553,7 +555,8 @@ namespace phoenix {
}

// *evil template hacking ensues*
_m_function_overrides[sym->address()] = [callback](vm& machine) {
_m_function_overrides[sym->address()] = [callback, sym](vm& machine) {
machine.push_call(sym);
if constexpr (std::is_same_v<void, R>) {
if constexpr (sizeof...(P) > 0) {
auto v = machine.pop_values_for_external<P...>();
Expand All @@ -568,6 +571,28 @@ namespace phoenix {
machine.push_value_from_external(callback());
}
}
machine.pop_call();
};

PX_LOGD("vm: overrode function ", sym->name());
}

/// \brief Overrides a function in Daedalus code with an external naked call.
///
/// Whenever the function with the given name would be called from within Daedalus code, redirect the call
/// to the given external callback handler instead.
///
/// \param name The name of the function to override.
/// \param callback The C++ function to register as the external.
void override_function(std::string_view name, const std::function<naked_call(vm&)>& callback) {
auto* sym = find_symbol_by_name(name);
if (sym == nullptr)
throw vm_exception {"symbol not found"};
if (sym->is_external())
throw vm_exception {"symbol is already an external"};

_m_function_overrides[sym->address()] = [callback](vm& machine) {
callback(machine);
};

PX_LOGD("vm: overrode function ", sym->name());
Expand Down Expand Up @@ -600,6 +625,8 @@ namespace phoenix {

PHOENIX_API void register_default_external_custom(const std::function<void(vm&, symbol&)>& callback);

PHOENIX_API void register_access_trap(const std::function<void (symbol &)> &callback);

/// \brief Registers a function to be called when script execution fails.
///
/// A variety of exceptions can occur within the VM while executing. The function passed to this handler can
Expand All @@ -620,6 +647,8 @@ namespace phoenix {
/// \param sym The symbol to unsafe_call.
PHOENIX_API void unsafe_call(const symbol* sym);

PHOENIX_API void unsafe_jump(uint32_t address);

/// \return the symbol referring to the global <tt>var C_NPC self</tt>.
PHOENIX_API inline symbol* global_self() {
return _m_self_sym;
Expand Down Expand Up @@ -918,6 +947,7 @@ namespace phoenix {
std::unordered_map<symbol*, std::function<void(vm&)>> _m_externals;
std::unordered_map<uint32_t, std::function<void(vm&)>> _m_function_overrides;
std::optional<std::function<void(vm&, symbol&)>> _m_default_external {std::nullopt};
std::function<void(symbol&)> _m_access_trap;
std::optional<std::function<vm_exception_strategy(vm&, const script_error&, const instruction&)>>
_m_exception_handler {std::nullopt};

Expand Down
7 changes: 7 additions & 0 deletions source/script.cc
Original file line number Diff line number Diff line change
Expand Up @@ -456,4 +456,11 @@ namespace phoenix {

std::get<std::shared_ptr<instance>>(_m_value) = inst;
}

void symbol::set_access_trap_enable(bool enable) noexcept {
if (enable)
_m_flags |= symbol_flag::access_trap;
else
_m_flags &= ~symbol_flag::access_trap;
}
} // namespace phoenix
19 changes: 14 additions & 5 deletions source/vm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ namespace phoenix {
pop_call();
}

void vm::unsafe_jump(uint32_t address) {
this->jump(address);
}

bool vm::exec() {
auto instr = instruction_at(_m_pc);

Expand Down Expand Up @@ -206,11 +210,8 @@ namespace phoenix {
if (cb != _m_function_overrides.end()) {
// Guard against exceptions during external invocation.
stack_guard guard {this, sym->rtype()};

push_call(sym);
// Call maybe naked.
cb->second(*this);
pop_call();

// The stack is left intact.
guard.inhibit();
} else {
Expand Down Expand Up @@ -260,7 +261,11 @@ namespace phoenix {
if (sym == nullptr) {
throw vm_exception {"pushv: no symbol found for index"};
}
push_reference(sym, 0);
if (sym->has_access_trap() && _m_access_trap) {
_m_access_trap(*sym);
} else {
push_reference(sym, 0);
}
break;
case opcode::movi:
case opcode::movvf: {
Expand Down Expand Up @@ -660,6 +665,10 @@ namespace phoenix {
_m_default_external = callback;
}

void vm::register_access_trap(const std::function<void (symbol &)> &callback) {
_m_access_trap = callback;
}

void vm::register_exception_handler(
const std::function<vm_exception_strategy(vm&, const script_error&, const instruction&)>& callback) {
_m_exception_handler = callback;
Expand Down

0 comments on commit e481bca

Please sign in to comment.