diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index f3b0a841981..3d998941eaf 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -295,6 +295,8 @@ struct controller_impl { } emit(self.irreversible_block, s); } + + wasmif.prune_wasm_cache(s->block_num); } void replay(std::function shutdown) { diff --git a/libraries/chain/eosio_contract.cpp b/libraries/chain/eosio_contract.cpp index 03e0fed7f7f..b5ac4937a46 100644 --- a/libraries/chain/eosio_contract.cpp +++ b/libraries/chain/eosio_contract.cpp @@ -150,6 +150,8 @@ void apply_eosio_setcode(apply_context& context) { EOS_ASSERT( account.code_version != code_id, set_exact_code, "contract is already running this version of code" ); + context.control.get_wasm_interface().account_code_change(account.code_version, code_id, context.control.head_block_num()); + db.modify( account, [&]( auto& a ) { /** TODO: consider whether a microsecond level local timestamp is sufficient to detect code version changes*/ // TODO: update setcode message to include the hash, then validate it in validate diff --git a/libraries/chain/include/eosio/chain/wasm_interface.hpp b/libraries/chain/include/eosio/chain/wasm_interface.hpp index 7e6991996af..b941cc7b13b 100644 --- a/libraries/chain/include/eosio/chain/wasm_interface.hpp +++ b/libraries/chain/include/eosio/chain/wasm_interface.hpp @@ -62,6 +62,12 @@ namespace eosio { namespace chain { //validates code -- does a WASM validation pass and checks the wasm against EOSIO specific constraints static void validate(const controller& control, const bytes& code); + //indicate when code has changed on an account, effectively reducing the reference count on + //the old one and increasing the reference count on the new one + void account_code_change(const digest_type& from, const digest_type& to, const uint32_t pending_block_num); + + void prune_wasm_cache(const uint32_t through_block_num); + //Calls apply or error on a given code void apply(const digest_type& code_id, const shared_string& code, apply_context& context); diff --git a/libraries/chain/include/eosio/chain/wasm_interface_private.hpp b/libraries/chain/include/eosio/chain/wasm_interface_private.hpp index c3af34d79ea..cf6c72d2905 100644 --- a/libraries/chain/include/eosio/chain/wasm_interface_private.hpp +++ b/libraries/chain/include/eosio/chain/wasm_interface_private.hpp @@ -19,10 +19,21 @@ using namespace fc; using namespace eosio::chain::webassembly; using namespace IR; using namespace Runtime; +using boost::multi_index_container; namespace eosio { namespace chain { struct wasm_interface_impl { + struct wasm_cache_entry { + digest_type code_hash; + unsigned reference_count = 0; + //must be set to UINT32_MAX when reference_count > 1 + uint32_t last_block_num_referenced = 0; + std::unique_ptr module; + }; + struct by_hash; + struct by_last_block_num; + wasm_interface_impl(wasm_interface::vm_type vm) { if(vm == wasm_interface::vm_type::wavm) runtime_interface = std::make_unique(); @@ -50,12 +61,14 @@ namespace eosio { namespace chain { return mem_image; } - std::unique_ptr& get_instantiated_module( const digest_type& code_id, + const std::unique_ptr& get_instantiated_module( const digest_type& code_id, const shared_string& code, transaction_context& trx_context ) { - auto it = instantiation_cache.find(code_id); - if(it == instantiation_cache.end()) { + wasm_cache_index::iterator it = wasm_instantiation_cache.find(code_id); + if(it == wasm_instantiation_cache.end()) + it = wasm_instantiation_cache.emplace(wasm_interface_impl::wasm_cache_entry{code_id, 1, UINT32_MAX, nullptr}).first; + if(!it->module) { auto timer_pause = fc::make_scoped_exit([&](){ trx_context.resume_billing_timer(); }); @@ -84,12 +97,24 @@ namespace eosio { namespace chain { } catch(const IR::ValidationException& e) { EOS_ASSERT(false, wasm_serialization_error, e.message.c_str()); } - it = instantiation_cache.emplace(code_id, runtime_interface->instantiate_module((const char*)bytes.data(), bytes.size(), parse_initial_memory(module))).first; + + wasm_instantiation_cache.modify(it, [&](auto& c) { + c.module = runtime_interface->instantiate_module((const char*)bytes.data(), bytes.size(), parse_initial_memory(module)); + }); } - return it->second; + return it->module; } std::unique_ptr runtime_interface; + + typedef boost::multi_index_container< + wasm_cache_entry, + indexed_by< + ordered_unique, member>, + ordered_non_unique, member> + > + > wasm_cache_index; + wasm_cache_index wasm_instantiation_cache; map> instantiation_cache; }; diff --git a/libraries/chain/include/eosio/chain/webassembly/wavm.hpp b/libraries/chain/include/eosio/chain/webassembly/wavm.hpp index f619e318b3f..205c825e79a 100644 --- a/libraries/chain/include/eosio/chain/webassembly/wavm.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/wavm.hpp @@ -23,14 +23,6 @@ class wavm_runtime : public eosio::chain::wasm_runtime_interface { std::unique_ptr instantiate_module(const char* code_bytes, size_t code_size, std::vector initial_memory) override; void immediately_exit_currently_running_module() override; - - struct runtime_guard { - runtime_guard(); - ~runtime_guard(); - }; - - private: - std::shared_ptr _runtime_guard; }; //This is a temporary hack for the single threaded implementation diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index feb9efbef8a..d7a402caf47 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -58,6 +58,30 @@ namespace eosio { namespace chain { my->get_instantiated_module(code_id, code, context.trx_context)->apply(context); } + void wasm_interface::account_code_change(const digest_type& from, const digest_type& to, const uint32_t pending_block_num) { + if(from != digest_type()) { + wasm_interface_impl::wasm_cache_index::iterator it = my->wasm_instantiation_cache.find(from); + if(it != my->wasm_instantiation_cache.end()) + my->wasm_instantiation_cache.modify(it, [&pending_block_num](auto& e) { + if(!--e.reference_count) + e.last_block_num_referenced = pending_block_num; + }); + } + if(to != digest_type()) { + auto success = my->wasm_instantiation_cache.emplace(wasm_interface_impl::wasm_cache_entry{to, 1, UINT32_MAX, nullptr}); + if(!success.second) + my->wasm_instantiation_cache.modify(success.first, [](auto& e) { + ++e.reference_count; + e.last_block_num_referenced = UINT32_MAX; + }); + } + } + + void wasm_interface::prune_wasm_cache(const uint32_t through_block_num) { + auto& blockidx = my->wasm_instantiation_cache.get(); + blockidx.erase(blockidx.begin(), blockidx.upper_bound(through_block_num)); + } + void wasm_interface::exit() { my->runtime_interface->immediately_exit_currently_running_module(); } diff --git a/libraries/chain/webassembly/wavm.cpp b/libraries/chain/webassembly/wavm.cpp index e614398c74e..c527beffd6b 100644 --- a/libraries/chain/webassembly/wavm.cpp +++ b/libraries/chain/webassembly/wavm.cpp @@ -12,7 +12,8 @@ #include "Runtime/Linker.h" #include "Runtime/Intrinsics.h" -#include +#include +#include using namespace IR; using namespace Runtime; @@ -21,14 +22,47 @@ namespace eosio { namespace chain { namespace webassembly { namespace wavm { running_instance_context the_running_instance_context; +namespace detail { +struct wavm_runtime_initializer { + wavm_runtime_initializer() { + Runtime::init(); + } +}; + +using live_module_ref = std::list::iterator; + +struct wavm_live_modules { + live_module_ref add_live_module(ModuleInstance* module_instance) { + return live_modules.insert(live_modules.begin(), asObject(module_instance)); + } + + void remove_live_module(live_module_ref it) { + live_modules.erase(it); + std::vector root; + std::copy(live_modules.begin(), live_modules.end(), std::back_inserter(root)); + Runtime::freeUnreferencedObjects(std::move(root)); + } + + std::list live_modules; +}; + +static wavm_live_modules the_wavm_live_modules; + +} + class wavm_instantiated_module : public wasm_instantiated_module_interface { public: wavm_instantiated_module(ModuleInstance* instance, std::unique_ptr module, std::vector initial_mem) : _initial_memory(initial_mem), _instance(instance), - _module(std::move(module)) + _module(std::move(module)), + _module_ref(detail::the_wavm_live_modules.add_live_module(instance)) {} + ~wavm_instantiated_module() { + detail::the_wavm_live_modules.remove_live_module(_module_ref); + } + void apply(apply_context& context) override { vector args = {Value(uint64_t(context.receiver)), Value(uint64_t(context.act.account)), @@ -79,30 +113,11 @@ class wavm_instantiated_module : public wasm_instantiated_module_interface { //_instance is deleted via WAVM's object garbage collection when wavm_rutime is deleted ModuleInstance* _instance; std::unique_ptr _module; + detail::live_module_ref _module_ref; }; - -wavm_runtime::runtime_guard::runtime_guard() { - // TODO clean this up - //check_wasm_opcode_dispositions(); - Runtime::init(); -} - -wavm_runtime::runtime_guard::~runtime_guard() { - Runtime::freeUnreferencedObjects({}); -} - -static weak_ptr __runtime_guard_ptr; -static std::mutex __runtime_guard_lock; - wavm_runtime::wavm_runtime() { - std::lock_guard l(__runtime_guard_lock); - if (__runtime_guard_ptr.use_count() == 0) { - _runtime_guard = std::make_shared(); - __runtime_guard_ptr = _runtime_guard; - } else { - _runtime_guard = __runtime_guard_ptr.lock(); - } + static detail::wavm_runtime_initializer the_wavm_runtime_initializer; } wavm_runtime::~wavm_runtime() {