From a348a389f4fa348b77f7d8afecf5934d14238256 Mon Sep 17 00:00:00 2001 From: Try Date: Sat, 19 Aug 2023 12:58:27 +0200 Subject: [PATCH] [#79] feat(vm): add transient and opaque instances --- include/phoenix/script.hh | 72 ++++++++++++++++- include/phoenix/vm.hh | 16 ++++ source/script.cc | 151 +++++++++++++++++++++++++++++++++++ source/vm.cc | 160 ++++++++++++++++++++++---------------- 4 files changed, 332 insertions(+), 67 deletions(-) diff --git a/include/phoenix/script.hh b/include/phoenix/script.hh index 06b53932..bee2bbc3 100644 --- a/include/phoenix/script.hh +++ b/include/phoenix/script.hh @@ -217,7 +217,17 @@ namespace phoenix { /// \brief A pointer which may be used by the user of this API void* user_ptr = nullptr; + protected: + PHOENIX_INTERNAL virtual std::uint8_t* data() { + return reinterpret_cast(this); + } + + PHOENIX_INTERNAL virtual const std::uint8_t* data() const { + return reinterpret_cast(this); + } + private: + friend class transient_instance; friend class symbol; friend class script; friend class vm; @@ -226,6 +236,54 @@ namespace phoenix { const std::type_info* _m_type {nullptr}; }; + /// \brief Represents an object associated with an instance in the script. + /// + /// Instances allocated with init_opaque will be backed up by this class with plain memory storage + class opaque_instance final : public instance { + public: + PHOENIX_INTERNAL opaque_instance(symbol const& sym, std::vector const& members); + PHOENIX_INTERNAL ~opaque_instance(); + + protected: + friend class symbol; + + PHOENIX_INTERNAL std::uint8_t* data() override { + return _m_storage.get(); + } + + PHOENIX_INTERNAL const std::uint8_t* data() const override { + return _m_storage.get(); + } + + private: + template + PHOENIX_INTERNAL T* construct_at(size_t offset, Args&&... args); + + std::unique_ptr _m_storage; + std::vector _m_strings; + }; + + /// \brief Represents object instance in the script with no defined backing to memory. + /// + /// Expected to be used for DMA mods or to emulate variable-like access to engine-functions. + class transient_instance : public instance { + public: + transient_instance(); + ~transient_instance(); + + protected: + friend class symbol; + + virtual void set_int(symbol const& sym, uint16_t index, std::int32_t value) = 0; + virtual std::int32_t get_int(symbol const& sym, uint16_t index) = 0; + + virtual void set_float(symbol const& sym, uint16_t index, float value) = 0; + virtual float get_float(symbol const& sym, uint16_t index) = 0; + + virtual void set_string(symbol const& sym, uint16_t index, std::string_view value) = 0; + virtual const std::string& get_string(symbol const& sym, uint16_t index) = 0; + }; + /// \brief The base class for all exceptions thrown by interacting with a script. struct script_error : public error { using error::error; @@ -573,8 +631,9 @@ namespace phoenix { if (*_m_registered_to != *context->_m_type) throw illegal_context_type {this, *context->_m_type}; + auto data_ptr = context->data(); std::uint32_t target_offset = offset_as_member() + index * sizeof(T); - return reinterpret_cast(reinterpret_cast(context.get()) + target_offset); + return reinterpret_cast(data_ptr + target_offset); } template @@ -584,8 +643,9 @@ namespace phoenix { if (*_m_registered_to != *context->_m_type) throw illegal_context_type {this, *context->_m_type}; + auto data_ptr = context->data(); std::uint32_t target_offset = offset_as_member() + index * sizeof(T); - return reinterpret_cast(reinterpret_cast(context.get()) + target_offset); + return reinterpret_cast(data_ptr + target_offset); } private: @@ -792,6 +852,14 @@ namespace phoenix { return find_symbol_by_index(inst->_m_symbol_index); } + [[nodiscard]] PHOENIX_API std::vector find_class_members(symbol const& cls); + + inline void register_as_opaque(std::string_view class_name) { + return register_as_opaque(find_symbol_by_name(class_name)); + } + + void register_as_opaque(symbol* sym); + protected: PHOENIX_INTERNAL script() = default; diff --git a/include/phoenix/vm.hh b/include/phoenix/vm.hh index eef2db9d..ed854268 100644 --- a/include/phoenix/vm.hh +++ b/include/phoenix/vm.hh @@ -205,6 +205,8 @@ namespace phoenix { return inst; } + std::shared_ptr init_opaque_instance(symbol* sym); + /// \brief Initializes an instance with the given type into \p instance /// \tparam _instance_t The type of the instance to initialize (ie. C_NPC). /// \param instance The instance to initialize. @@ -962,6 +964,20 @@ namespace phoenix { } } + [[nodiscard]] PHOENIX_API std::int32_t + get_int(std::shared_ptr& context, + std::variant>& value, + uint16_t index); + [[nodiscard]] PHOENIX_API float + get_float(std::shared_ptr& context, + std::variant>& value, + uint16_t index); + + PHOENIX_API void set_int(std::shared_ptr& context, symbol* ref, uint16_t index, std::int32_t value); + PHOENIX_API void set_float(std::shared_ptr& context, symbol* ref, uint16_t index, float value); + PHOENIX_API void + set_string(std::shared_ptr& context, symbol* ref, uint16_t index, std::string_view value); + private: std::array _m_stack; uint16_t _m_stack_ptr {0}; diff --git a/source/script.cc b/source/script.cc index 307d4d4e..18a2a476 100644 --- a/source/script.cc +++ b/source/script.cc @@ -211,6 +211,58 @@ namespace phoenix { return syms; } + std::vector script::find_class_members(const symbol& cls) { + std::vector members {}; + + for (auto& sym : _m_symbols) { + if (!sym.is_member() || sym.parent() != cls.index()) + continue; + members.push_back(&sym); + } + + return members; + } + + void script::register_as_opaque(symbol* sym) { + auto members = find_class_members(*sym); + + auto registered_to = &typeid(opaque_instance); + size_t class_size = 0; + + for (auto* member : members) { + member->_m_registered_to = registered_to; + + switch (member->type()) { + case datatype::void_: + case datatype::float_: + case datatype::integer: + case datatype::class_: + case datatype::function: + case datatype::prototype: + case datatype::instance: + member->_m_member_offset = class_size; + class_size += 4 * member->count(); + break; + case datatype::string: { + auto align = alignof(std::string); + auto offset = class_size; + + auto remain = offset % align; + auto offset_remain = remain == 0 ? 0 : align - remain; + + class_size += offset_remain; + member->_m_member_offset = class_size; + + class_size += sizeof(std::string) * member->count(); + break; + } + } + } + + sym->_m_registered_to = registered_to; + sym->_m_class_size = class_size; + } + symbol* script::add_temporary_strings_symbol() { symbol sym {}; sym._m_name = "$PHOENIX_FAKE_STRINGS"; @@ -345,6 +397,11 @@ namespace phoenix { if (context == nullptr) { throw no_context(this); } + + if (context->symbol_index() == unset && context->_m_type == &typeid(transient_instance)) { + return reinterpret_cast(*context).get_string(*this, index); + } + return *get_member_ptr(index, context); } else { return std::get>(_m_value)[index]; @@ -363,6 +420,11 @@ namespace phoenix { if (context == nullptr) { throw no_context(this); } + + if (context->symbol_index() == unset && context->_m_type == &typeid(transient_instance)) { + return reinterpret_cast(*context).get_float(*this, index); + } + return *get_member_ptr(index, context); } else { return std::get>(_m_value)[index]; @@ -381,6 +443,11 @@ namespace phoenix { if (context == nullptr) { throw no_context(this); } + + if (context->symbol_index() == unset && context->_m_type == &typeid(transient_instance)) { + return reinterpret_cast(*context).get_int(*this, index); + } + return *get_member_ptr(index, context); } else { return std::get>(_m_value)[index]; @@ -399,6 +466,12 @@ namespace phoenix { if (context == nullptr) { throw no_context(this); } + + if (context->symbol_index() == unset && context->_m_type == &typeid(transient_instance)) { + reinterpret_cast(*context).set_string(*this, index, value); + return; + } + *get_member_ptr(index, context) = value; } else { std::get>(_m_value).get()[index] = value; @@ -417,6 +490,12 @@ namespace phoenix { if (context == nullptr) { throw no_context(this); } + + if (context->symbol_index() == unset && context->_m_type == &typeid(transient_instance)) { + reinterpret_cast(*context).set_float(*this, index, value); + return; + } + *get_member_ptr(index, context) = value; } else { std::get>(_m_value)[index] = value; @@ -435,6 +514,12 @@ namespace phoenix { if (context == nullptr) { throw no_context(this); } + + if (context->symbol_index() == unset && context->_m_type == &typeid(transient_instance)) { + reinterpret_cast(*context).set_int(*this, index, value); + return; + } + *get_member_ptr(index, context) = value; } else { std::get>(_m_value)[index] = value; @@ -463,4 +548,70 @@ namespace phoenix { else _m_flags &= ~symbol_flag::access_trap; } + + opaque_instance::opaque_instance(const symbol& sym, const std::vector& members) { + size_t str_count = 0; + for (auto* member : members) { + if (member->type() != datatype::string) + continue; + str_count += member->count(); + } + + _m_storage.reset(new uint8_t[sym.class_size()]()); + _m_strings.resize(str_count, nullptr); + + str_count = 0; + for (auto* member : members) { + unsigned offset = member->offset_as_member(); + + for (auto i = 0U; i < member->count(); ++i) { + switch (member->type()) { + case datatype::float_: + this->construct_at(offset, 0); + offset += 4; + break; + case datatype::integer: + this->construct_at(offset, 0); + offset += 4; + break; + case datatype::string: + _m_strings[str_count] = this->construct_at(offset, ""); + str_count++; + offset += sizeof(std::string); + break; + case datatype::function: + this->construct_at(offset, 0); + offset += 4; + break; + case datatype::class_: + case datatype::prototype: + case datatype::instance: + case datatype::void_: + this->construct_at(offset); + offset += 4; + break; + } + } + } + } + + opaque_instance::~opaque_instance() { + for (auto& i : _m_strings) + i->std::string::~string(); + } + + template + T* opaque_instance::construct_at(size_t offset, Args&&... args) { + auto align = alignof(T); + auto remain = offset % align; + auto real_offset = remain == 0 ? offset : offset + (align - remain); + return new (static_cast(&_m_storage[real_offset])) T(std::forward(args)...); + } + + transient_instance::transient_instance() { + _m_type = &typeid(transient_instance); + } + + transient_instance::~transient_instance() {} + } // namespace phoenix diff --git a/source/vm.cc b/source/vm.cc index dec717ee..b31d7e5a 100644 --- a/source/vm.cc +++ b/source/vm.cc @@ -82,6 +82,22 @@ namespace phoenix { _m_temporary_strings = add_temporary_strings_symbol(); } + std::shared_ptr vm::init_opaque_instance(symbol* sym) { + auto cls = sym; + while (cls != nullptr && cls->type() != datatype::class_) { + cls = find_symbol_by_index(cls->parent()); + } + if (cls == nullptr) { + // We're probably trying to initialize $INSTANCE_HELP which is not permitted + throw vm_exception {"Cannot init " + sym->name() + + ": parent class not found (did you try to initialize $INSTANCE_HELP?)"}; + } + // create the instance + auto inst = std::make_shared(*cls, find_class_members(*cls)); + init_instance(inst, sym); + return inst; + } + void vm::unsafe_call(const symbol* sym) { push_call(sym); jump(sym->address()); @@ -218,7 +234,6 @@ namespace phoenix { if (sym == nullptr) { throw vm_exception {"bl: no symbol found for address " + std::to_string(instr.address)}; } - unsafe_call(sym); } @@ -271,52 +286,19 @@ namespace phoenix { case opcode::movvf: { auto [ref, idx, context] = pop_reference(); auto value = pop_int(); - - if (ref->is_const() && !(_m_flags & execution_flag::vm_ignore_const_specifier)) { - throw illegal_const_access(ref); - } - - if (!ref->is_member() || context != nullptr || - !(_m_flags & execution_flag::vm_allow_null_instance_access)) { - ref->set_int(value, idx, context); - } else if (ref->is_member()) { - PX_LOGE("vm: accessing member \"", ref->name(), "\" without an instance set"); - } - + set_int(context, ref, idx, value); break; } case opcode::movf: { auto [ref, idx, context] = pop_reference(); auto value = pop_float(); - - if (ref->is_const() && !(_m_flags & execution_flag::vm_ignore_const_specifier)) { - throw illegal_const_access(ref); - } - - if (!ref->is_member() || context != nullptr || - !(_m_flags & execution_flag::vm_allow_null_instance_access)) { - ref->set_float(value, idx, context); - } else if (ref->is_member()) { - PX_LOGE("vm: accessing member \"", ref->name(), "\" without an instance set"); - } - + set_float(context, ref, idx, value); break; } case opcode::movs: { auto [target, target_idx, context] = pop_reference(); auto source = pop_string(); - - if (target->is_const() && !(_m_flags & execution_flag::vm_ignore_const_specifier)) { - throw illegal_const_access(target); - } - - if (!target->is_member() || context != nullptr || - !(_m_flags & execution_flag::vm_allow_null_instance_access)) { - target->set_string(source, target_idx, context); - } else if (target->is_member()) { - PX_LOGE("vm: accessing member \"", target->name(), "\" without an instance set"); - } - + set_string(context, target, target_idx, source); break; } case opcode::movss: @@ -518,20 +500,7 @@ namespace phoenix { daedalus_stack_frame v = std::move(_m_stack[--_m_stack_ptr]); if (v.reference) { - auto* sym = std::get(v.value); - - // compatibility: sometimes the context might be zero, but we can't fail so when - // the compatibility flag is set, we just return 0 - if (sym->is_member() && v.context == nullptr) { - if (!(_m_flags & execution_flag::vm_allow_null_instance_access)) { - throw no_context {sym}; - } - - PX_LOGE("vm: accessing member \"", sym->name(), "\" without an instance set"); - return 0; - } - - return sym->get_int(v.index, v.context); + return get_int(v.context, v.value, v.index); } else if (std::holds_alternative(v.value)) { return std::get(v.value); } else { @@ -547,20 +516,7 @@ namespace phoenix { daedalus_stack_frame v = std::move(_m_stack[--_m_stack_ptr]); if (v.reference) { - auto* sym = std::get(v.value); - - // compatibility: sometimes the context might be zero, but we can't fail so when - // the compatibility flag is set, we just return 0 - if (sym->is_member() && v.context == nullptr) { - if (!(_m_flags & execution_flag::vm_allow_null_instance_access)) { - throw no_context {sym}; - } - - PX_LOGE("vm: accessing member \"", sym->name(), "\" without an instance set"); - return 0; - } - - return sym->get_float(v.index, v.context); + return get_float(v.context, v.value, v.index); } else if (std::holds_alternative(v.value)) { return std::get(v.value); } else if (std::holds_alternative(v.value)) { @@ -623,6 +579,80 @@ namespace phoenix { return s->get_string(i, context); } + std::int32_t vm::get_int(std::shared_ptr& context, + std::variant>& value, + uint16_t index) { + auto* sym = std::get(value); + + // compatibility: sometimes the context might be zero, but we can't fail so when + // the compatibility flag is set, we just return 0 + if (sym->is_member() && context == nullptr) { + if (!(_m_flags & execution_flag::vm_allow_null_instance_access)) { + throw no_context {sym}; + } + + PX_LOGE("vm: accessing member \"", sym->name(), "\" without an instance set"); + return 0; + } + + return sym->get_int(index, context); + } + + float vm::get_float(std::shared_ptr& context, + std::variant>& value, + uint16_t index) { + auto* sym = std::get(value); + + // compatibility: sometimes the context might be zero, but we can't fail so when + // the compatibility flag is set, we just return 0 + if (sym->is_member() && context == nullptr) { + if (!(_m_flags & execution_flag::vm_allow_null_instance_access)) { + throw no_context {sym}; + } + + PX_LOGE("vm: accessing member \"", sym->name(), "\" without an instance set"); + return 0; + } + + return sym->get_float(index, context); + } + + void vm::set_int(std::shared_ptr& context, symbol* ref, uint16_t index, std::int32_t value) { + if (ref->is_const() && !(_m_flags & execution_flag::vm_ignore_const_specifier)) { + throw illegal_const_access(ref); + } + + if (!ref->is_member() || context != nullptr || !(_m_flags & execution_flag::vm_allow_null_instance_access)) { + ref->set_int(value, index, context); + } else if (ref->is_member()) { + PX_LOGE("vm: accessing member \"", ref->name(), "\" without an instance set"); + } + } + + void vm::set_float(std::shared_ptr& context, symbol* ref, uint16_t index, float value) { + if (ref->is_const() && !(_m_flags & execution_flag::vm_ignore_const_specifier)) { + throw illegal_const_access(ref); + } + + if (!ref->is_member() || context != nullptr || !(_m_flags & execution_flag::vm_allow_null_instance_access)) { + ref->set_float(value, index, context); + } else if (ref->is_member()) { + PX_LOGE("vm: accessing member \"", ref->name(), "\" without an instance set"); + } + } + + void vm::set_string(std::shared_ptr& context, symbol* ref, uint16_t index, std::string_view value) { + if (ref->is_const() && !(_m_flags & execution_flag::vm_ignore_const_specifier)) { + throw illegal_const_access(ref); + } + + if (!ref->is_member() || context != nullptr || !(_m_flags & execution_flag::vm_allow_null_instance_access)) { + ref->set_string(value, index, context); + } else if (ref->is_member()) { + PX_LOGE("vm: accessing member \"", ref->name(), "\" without an instance set"); + } + } + void vm::jump(std::uint32_t address) { if (address > size()) { throw vm_exception {"Cannot jump to " + std::to_string(address) + ": illegal address"};