提交 243b1b6a 编写于 作者: M Matt Witherspoon

Reduce wasm table vmem usage

Previously, a 68GB virtual memory map was created per wasm (contract) instance. This eats up the virtual memory space exceptionally quick.

Now, only allocate 12KB per wasm/contract instance. This is enough for 1024 table elements which is hopefully plenty. The side effect is that every indirect function call now has an additional runtime check on its value (checking if it is less than 1024). This seems like an acceptable tradeoff.

Also I have removed the 4GB alignment for the table and replaced it with 4KB. I wasn’t finding any benefit to that 4GB alignment. The generated x86_64 code was simply in the form of *8(%base, %index) so I probably don’t even need 4KB.
上级 db05664f
......@@ -31,6 +31,8 @@ add_definitions(${LLVM_DEFINITIONS})
add_definitions(-DRUNTIME_API=DLL_EXPORT)
target_include_directories( Runtime PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../../chain/include )
# Link against the LLVM libraries
llvm_map_components_to_libnames(LLVM_LIBS support core passes mcjit native DebugInfoDWARF)
target_link_libraries(Runtime Platform Logging IR ${LLVM_LIBS})
......@@ -2,6 +2,7 @@
#include "Runtime.h"
#include "Platform/Platform.h"
#include "RuntimePrivate.h"
#include <eosio/chain/wasm_eosio_constraints.hpp>
namespace Runtime
{
......@@ -17,13 +18,9 @@ namespace Runtime
{
TableInstance* table = new TableInstance(type);
// In 64-bit, allocate enough address-space to safely access 32-bit table indices without bounds checking, or 16MB (4M elements) if the host is 32-bit.
const Uptr tableMaxBytes = HAS_64BIT_ADDRESS_SPACE ? Uptr(U64(sizeof(TableInstance::FunctionElement)) << 32) : 16*1024*1024;
const Uptr tableMaxBytes = sizeof(TableInstance::FunctionElement)*eosio::chain::wasm_constraints::maximum_table_elements;
// On a 64 bit runtime, align the table base to a 4GB boundary, so the lower 32-bits will all be zero. Maybe it will allow better code generation?
// Note that this reserves a full extra 4GB, but only uses (4GB-1 page) for alignment, so there will always be a guard page at the end to
// protect against unaligned loads/stores that straddle the end of the address-space.
const Uptr alignmentBytes = HAS_64BIT_ADDRESS_SPACE ? Uptr(4ull*1024*1024*1024) : (Uptr(1) << Platform::getPageSizeLog2());
const Uptr alignmentBytes = 1U << Platform::getPageSizeLog2();
table->baseAddress = (TableInstance::FunctionElement*)allocateVirtualPagesAligned(tableMaxBytes,alignmentBytes,table->reservedBaseAddress,table->reservedNumPlatformPages);
table->endOffset = tableMaxBytes;
if(!table->baseAddress) { delete table; return nullptr; }
......
......@@ -171,3 +171,77 @@ static const char memory_table_import[] = R"=====(
(memory (import "nom" "memory") 0)
)
)=====";
static const char table_checker_wast[] = R"=====(
(module
(import "env" "assert" (func $assert (param i32 i32)))
(import "env" "printi" (func $printi (param i64)))
(type $SIG$vj (func (param i64)))
(table 1024 anyfunc)
(memory $0 1)
(export "apply" (func $apply))
(func $apply (param $0 i64) (param $1 i64)
(call_indirect $SIG$vj
(i64.shr_u
(get_local $1)
(i64.const 32)
)
(i32.wrap/i64
(get_local $1)
)
)
)
(func $apple (type $SIG$vj) (param $0 i64)
(call $assert
(i64.eq
(get_local $0)
(i64.const 555)
)
(i32.const 0)
)
)
(func $bannna (type $SIG$vj) (param $0 i64)
(call $assert
(i64.eq
(get_local $0)
(i64.const 7777)
)
(i32.const 0)
)
)
(elem (i32.const 0) $apple)
(elem (i32.const 1022) $apple $bannna)
)
)=====";
static const char table_checker_small_wast[] = R"=====(
(module
(import "env" "assert" (func $assert (param i32 i32)))
(import "env" "printi" (func $printi (param i64)))
(type $SIG$vj (func (param i64)))
(table 128 anyfunc)
(memory $0 1)
(export "apply" (func $apply))
(func $apply (param $0 i64) (param $1 i64)
(call_indirect $SIG$vj
(i64.shr_u
(get_local $1)
(i64.const 32)
)
(i32.wrap/i64
(get_local $1)
)
)
)
(func $apple (type $SIG$vj) (param $0 i64)
(call $assert
(i64.eq
(get_local $0)
(i64.const 555)
)
(i32.const 0)
)
)
(elem (i32.const 0) $apple)
)
)=====";
\ No newline at end of file
#include <boost/test/unit_test.hpp>
#include <eosio/testing/tester.hpp>
#include <eosio/chain/contracts/abi_serializer.hpp>
#include <eosio/chain/wasm_eosio_constraints.hpp>
#include <eosio/chain/exceptions.hpp>
#include <asserter/asserter.wast.hpp>
#include <asserter/asserter.abi.hpp>
......@@ -781,4 +782,123 @@ BOOST_FIXTURE_TEST_CASE(noop, tester) try {
} FC_LOG_AND_RETHROW()
BOOST_FIXTURE_TEST_CASE( check_table_maximum, tester ) try {
produce_blocks(2);
create_accounts( {N(tbl)}, asset::from_string("1000.0000 EOS") );
transfer( N(inita), N(tbl), "10.0000 EOS", "memo" );
produce_block();
set_code(N(tbl), table_checker_wast);
produce_blocks(1);
{
signed_transaction trx;
action act;
act.name = 555ULL<<32 | 0ULL; //top 32 is what we assert against, bottom 32 is indirect call index
act.account = N(tbl);
act.authorization = vector<permission_level>{{N(tbl),config::active_name}};
trx.actions.push_back(act);
set_tapos(trx);
trx.sign(get_private_key( N(tbl), "active" ), chain_id_type());
push_transaction(trx);
}
produce_blocks(1);
{
signed_transaction trx;
action act;
act.name = 555ULL<<32 | 1022ULL; //top 32 is what we assert against, bottom 32 is indirect call index
act.account = N(tbl);
act.authorization = vector<permission_level>{{N(tbl),config::active_name}};
trx.actions.push_back(act);
set_tapos(trx);
trx.sign(get_private_key( N(tbl), "active" ), chain_id_type());
push_transaction(trx);
}
produce_blocks(1);
{
signed_transaction trx;
action act;
act.name = 7777ULL<<32 | 1023ULL; //top 32 is what we assert against, bottom 32 is indirect call index
act.account = N(tbl);
act.authorization = vector<permission_level>{{N(tbl),config::active_name}};
trx.actions.push_back(act);
set_tapos(trx);
trx.sign(get_private_key( N(tbl), "active" ), chain_id_type());
push_transaction(trx);
}
produce_blocks(1);
{
signed_transaction trx;
action act;
act.name = 7778ULL<<32 | 1023ULL; //top 32 is what we assert against, bottom 32 is indirect call index
act.account = N(tbl);
act.authorization = vector<permission_level>{{N(tbl),config::active_name}};
trx.actions.push_back(act);
set_tapos(trx);
trx.sign(get_private_key( N(tbl), "active" ), chain_id_type());
//should fail, a check to make sure assert() in wasm is being evaluated correctly
BOOST_CHECK_THROW(push_transaction(trx), fc::assert_exception);
}
produce_blocks(1);
{
signed_transaction trx;
action act;
act.name = 133ULL<<32 | 5ULL; //top 32 is what we assert against, bottom 32 is indirect call index
act.account = N(tbl);
act.authorization = vector<permission_level>{{N(tbl),config::active_name}};
trx.actions.push_back(act);
set_tapos(trx);
trx.sign(get_private_key( N(tbl), "active" ), chain_id_type());
//should fail, this element index (5) does not exist
BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_execution_error);
}
produce_blocks(1);
{
signed_transaction trx;
action act;
act.name = eosio::chain::wasm_constraints::maximum_table_elements+54334;
act.account = N(tbl);
act.authorization = vector<permission_level>{{N(tbl),config::active_name}};
trx.actions.push_back(act);
set_tapos(trx);
trx.sign(get_private_key( N(tbl), "active" ), chain_id_type());
//should fail, this element index is out of range
BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_execution_error);
}
produce_blocks(1);
set_code(N(tbl), table_checker_small_wast);
produce_blocks(1);
{
signed_transaction trx;
action act;
act.name = 888ULL;
act.account = N(tbl);
act.authorization = vector<permission_level>{{N(tbl),config::active_name}};
trx.actions.push_back(act);
set_tapos(trx);
trx.sign(get_private_key( N(tbl), "active" ), chain_id_type());
//an element that is out of range and has no mmap access permission either (should be a trapped segv)
BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_execution_error);
}
} FC_LOG_AND_RETHROW()
BOOST_AUTO_TEST_SUITE_END()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册