Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

WASM Add Memory Pages #514

Merged
merged 3 commits into from
Sep 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions contracts/eoslib/memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ extern "C" {
* @{
*/

/**
* Allocate page(s) of memory to accommodate the
* requested number of bytes.
* @brief Allocate page memory
* @param num_bytes Number of bytes to add.
*
* @return void pointer to the previous end of allocated bytes
*
* Example:
* @code
* // allocate a whole new page, the returned offset is the pointer to the
* // newly allocated page
* char* new_page = static_cast<char*>(sbrk(65536));
* memset(new_page, 0, 65536);
* @endcode
*/
void* sbrk( uint32_t num_bytes );

/**
* Copy a block of memory from source to destination.
* @brief Copy a block of memory from source to destination.
Expand Down
1 change: 1 addition & 0 deletions libraries/chain/include/eos/chain/exceptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ namespace eos { namespace chain {
FC_DECLARE_DERIVED_EXCEPTION( tx_scheduling_exception, eos::chain::transaction_exception, 3030013, "transaction failed during sheduling" )
FC_DECLARE_DERIVED_EXCEPTION( tx_unknown_argument, eos::chain::transaction_exception, 3030014, "transaction provided an unknown value to a system call" )
FC_DECLARE_DERIVED_EXCEPTION( tx_resource_exhausted, eos::chain::transaction_exception, 3030015, "transaction exhausted allowed resources" )
FC_DECLARE_DERIVED_EXCEPTION( page_memory_error, eos::chain::transaction_exception, 3030016, "error in WASM page memory" )

FC_DECLARE_DERIVED_EXCEPTION( invalid_pts_address, eos::chain::utility_exception, 3060001, "invalid pts address" )
FC_DECLARE_DERIVED_EXCEPTION( insufficient_feeds, eos::chain::chain_exception, 37006, "insufficient feeds" )
Expand Down
6 changes: 5 additions & 1 deletion libraries/chain/include/eos/chain/wasm_interface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

namespace eos { namespace chain {

class chain_controller;
class chain_controller;
class wasm_memory;

/**
* @class wasm_interface
*
Expand Down Expand Up @@ -43,6 +45,8 @@ class wasm_interface {
Runtime::MemoryInstance* current_memory = nullptr;
Runtime::ModuleInstance* current_module = nullptr;
ModuleState* current_state = nullptr;
wasm_memory* current_memory_management = nullptr;


private:
void load( const AccountName& name, const chainbase::database& db );
Expand Down
97 changes: 92 additions & 5 deletions libraries/chain/wasm_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ namespace eos { namespace chain {
using namespace Runtime;
typedef boost::multiprecision::cpp_bin_float_50 DOUBLE;

class wasm_memory
{
public:
explicit wasm_memory(wasm_interface& interface);
wasm_memory(const wasm_memory&) = delete;
wasm_memory(wasm_memory&&) = delete;
~wasm_memory();
U32 sbrk(I32 num_bytes);
private:
static U32 limit_32bit_address(Uptr address);

static const U32 _max_memory = 1024 * 1024;
wasm_interface& _wasm_interface;
Uptr _num_pages;
const U32 _min_bytes;
U32 _num_bytes;
};

wasm_interface::wasm_interface() {
}

Expand All @@ -28,13 +46,18 @@ namespace eos { namespace chain {
const int CHECKTIME_LIMIT = 18000;
#endif

DEFINE_INTRINSIC_FUNCTION0(env,checktime,checktime,none) {
auto dur = wasm_interface::get().current_execution_time();
if (dur > CHECKTIME_LIMIT) {
wlog("checktime called ${d}", ("d", dur));
throw checktime_exceeded();
void checktime(int64_t duration)
{
if (duration > CHECKTIME_LIMIT) {
wlog("checktime called ${d}", ("d", duration));
throw checktime_exceeded();
}
}

DEFINE_INTRINSIC_FUNCTION0(env,checktime,checktime,none) {
checktime(wasm_interface::get().current_execution_time());
}

template <typename Function, typename KeyType, int numberOfKeys>
int32_t validate(int32_t valueptr, int32_t valuelen, Function func) {

Expand Down Expand Up @@ -321,6 +344,16 @@ DEFINE_INTRINSIC_FUNCTION3(env,memset,memset,i32,i32,rel_ptr,i32,value,i32,len)
return rel_ptr;
}

DEFINE_INTRINSIC_FUNCTION1(env,sbrk,sbrk,i32,i32,num_bytes) {
auto& wasm = wasm_interface::get();

FC_ASSERT( num_bytes >= 0, "sbrk can only allocate memory, not reduce" );
FC_ASSERT( wasm.current_memory_management != nullptr, "sbrk can only be called during the scope of wasm_interface::vm_call" );
U32 previous_bytes_allocated = wasm.current_memory_management->sbrk(num_bytes);
checktime(wasm.current_execution_time());
return previous_bytes_allocated;
}


/**
* Transaction C API implementation
Expand Down Expand Up @@ -552,6 +585,7 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) {

void wasm_interface::vm_call( const char* name ) {
try {
std::unique_ptr<wasm_memory> wasm_memory_mgmt;
try {
/*
name += "_" + std::string( current_validate_context->msg.code ) + "_";
Expand All @@ -577,8 +611,11 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) {
memcpy( memstart, state.init_memory.data(), state.mem_end);

checktimeStart = fc::time_point::now();
wasm_memory_mgmt.reset(new wasm_memory(*this));

Runtime::invokeFunction(call,args);
wasm_memory_mgmt.reset();
checktime(current_execution_time());
} catch( const Runtime::Exception& e ) {
edump((std::string(describeExceptionCause(e.cause))));
edump((e.callStack));
Expand Down Expand Up @@ -732,4 +769,54 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) {
current_state = &state;
}

wasm_memory::wasm_memory(wasm_interface& interface)
: _wasm_interface(interface)
, _num_pages(Runtime::getMemoryNumPages(interface.current_memory))
, _min_bytes(limit_32bit_address(_num_pages << numBytesPerPageLog2))
{
_wasm_interface.current_memory_management = this;
_num_bytes = _min_bytes;
}

wasm_memory::~wasm_memory()
{
if (_num_bytes > _min_bytes)
sbrk((I32)_min_bytes - (I32)_num_bytes);

_wasm_interface.current_memory_management = nullptr;
}

U32 wasm_memory::sbrk(I32 num_bytes)
{
const U32 previous_num_bytes = _num_bytes;
if(Runtime::getMemoryNumPages(_wasm_interface.current_memory) != _num_pages)
throw eos::chain::page_memory_error();

// Round the absolute value of num_bytes to an alignment boundary, and ensure it won't allocate too much or too little memory.
num_bytes = (num_bytes + 7) & ~7;
if(num_bytes > 0 && previous_num_bytes > _max_memory - num_bytes)
throw eos::chain::page_memory_error();
else if(num_bytes < 0 && previous_num_bytes < _min_bytes - num_bytes)
throw eos::chain::page_memory_error();

// Update the number of bytes allocated, and compute the number of pages needed for it.
_num_bytes += num_bytes;
const Uptr num_desired_pages = (_num_bytes + IR::numBytesPerPage - 1) >> IR::numBytesPerPageLog2;

// Grow or shrink the memory object to the desired number of pages.
if(num_desired_pages > _num_pages)
Runtime::growMemory(_wasm_interface.current_memory, num_desired_pages - _num_pages);
else if(num_desired_pages < _num_pages)
Runtime::shrinkMemory(_wasm_interface.current_memory, _num_pages - num_desired_pages);

_num_pages = num_desired_pages;

return previous_num_bytes;
}

U32 wasm_memory::limit_32bit_address(Uptr address)
{
return (U32)(address > UINT32_MAX ? UINT32_MAX : address);
}

} }
35 changes: 32 additions & 3 deletions tests/api_tests/api_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ BOOST_FIXTURE_TEST_CASE(test_memcpy_overlap_start, testing_fixture)
{
try {
MEMORY_TEST_RUN(testolstart, memory_test_wast);
BOOST_FAIL("memcpy should have thrown assert acception");
BOOST_FAIL("memcpy should have thrown assert exception");
}
catch(fc::assert_exception& ex)
{
Expand All @@ -502,15 +502,44 @@ BOOST_FIXTURE_TEST_CASE(test_memcpy_overlap_end, testing_fixture)
{
try {
MEMORY_TEST_RUN(testolend, memory_test_wast);
BOOST_FAIL("memcpy should have thrown assert acception");
BOOST_FAIL("memcpy should have thrown assert exception");
}
catch(fc::assert_exception& ex)
{
BOOST_REQUIRE(ex.to_detail_string().find("overlap of memory range is undefined") != std::string::npos);
}
}

//Test intrinsic provided memset and memcpy
//Test logic for memory.hpp adding extra pages of memory
MEMORY_TEST_CASE(test_extended_memory, testextmem, extended_memory_test_wast)

//Test logic for extra pages of memory
MEMORY_TEST_CASE(test_page_memory, testpagemem, extended_memory_test_wast)

//Test logic for exceeding extra pages of memory
BOOST_FIXTURE_TEST_CASE(test_page_memory_exceeded, testing_fixture)
{
try {
MEMORY_TEST_RUN(testmemexc, extended_memory_test_wast);
BOOST_FAIL("sbrk should have thrown exception");
}
catch (eos::chain::page_memory_error& )
{
// expected behavior
}
}

//Test logic for preventing reducing page memory
BOOST_FIXTURE_TEST_CASE(test_page_memory_negative_bytes, testing_fixture)
{
try {
MEMORY_TEST_RUN(testnegbytes, extended_memory_test_wast);
BOOST_FAIL("sbrk should have thrown exception");
}
catch (fc::assert_exception& ex)
{
BOOST_REQUIRE(ex.to_detail_string().find("not reduce") != std::string::npos);
}
}

BOOST_AUTO_TEST_SUITE_END()
89 changes: 89 additions & 0 deletions tests/api_tests/extended_memory_test/extended_memory_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
#include <eoslib/memory.hpp>

extern "C" {

const uint32_t _64K = 65536;

void init()
{
}
Expand Down Expand Up @@ -103,6 +106,71 @@ extern "C" {
assert(ptr10 == nullptr, "should not have allocated a 10 char buf");
}

void test_page_memory()
{
auto prev = sbrk(0);

assert(reinterpret_cast<uint32_t>(prev) == _64K, "Should initially have 1 64K page allocated");

prev = sbrk(1);

assert(reinterpret_cast<uint32_t>(prev) == _64K, "Should still point to the end of 1st 64K page");

prev = sbrk(2);

assert(reinterpret_cast<uint32_t>(prev) == _64K + 8, "Should point to 8 past the end of 1st 64K page");

prev = sbrk(_64K - 17);

assert(reinterpret_cast<uint32_t>(prev) == _64K + 16, "Should point to 16 past the end of 1st 64K page");

prev = sbrk(1);

assert(reinterpret_cast<uint32_t>(prev) == 2*_64K, "Should point to the end of 2nd 64K page");

prev = sbrk(_64K);

assert(reinterpret_cast<uint32_t>(prev) == 2*_64K + 8, "Should point to 8 past the end of the 2nd 64K page");

prev = sbrk(_64K - 15);

assert(reinterpret_cast<uint32_t>(prev) == 3*_64K + 8, "Should point to 8 past the end of the 3rd 64K page");

prev = sbrk(2*_64K - 1);

assert(reinterpret_cast<uint32_t>(prev) == 4*_64K, "Should point to the end of 4th 64K page");

prev = sbrk(2*_64K);

assert(reinterpret_cast<uint32_t>(prev) == 6*_64K, "Should point to the end of 6th 64K page");

prev = sbrk(2*_64K + 1);

assert(reinterpret_cast<uint32_t>(prev) == 8*_64K, "Should point to the end of 8th 64K page");

prev = sbrk(6*_64K - 15);

assert(reinterpret_cast<uint32_t>(prev) == 10*_64K + 8, "Should point to 8 past the end of 13th 64K page");

prev = sbrk(0);

assert(reinterpret_cast<uint32_t>(prev) == 16*_64K, "Should point to the end of 16th 64K page");
}

void test_page_memory_exceeded()
{
auto prev = sbrk(15*_64K);
assert(reinterpret_cast<uint32_t>(prev) == _64K, "Should have allocated up to the 1M of memory limit");
sbrk(1);
assert(0, "Should have thrown exception for trying to allocate more than 1M of memory");
}

void test_page_memory_negative_bytes()
{
sbrk(-1);
assert(0, "Should have thrown exception for trying to remove memory");
}

/// The apply method implements the dispatch of events to this contract
void apply( uint64_t code, uint64_t action )
{
Expand All @@ -113,5 +181,26 @@ extern "C" {
test_extended_memory();
}
}
else if( code == N(testpagemem) )
{
if( action == N(transfer) )
{
test_page_memory();
}
}
else if( code == N(testmemexc) )
{
if( action == N(transfer) )
{
test_page_memory_exceeded();
}
}
else if( code == N(testnegbytes) )
{
if( action == N(transfer) )
{
test_page_memory_negative_bytes();
}
}
}
}