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

Commit

Permalink
Merge pull request #514 from brianjohnson5972/504_wasm_add_mem_pages
Browse files Browse the repository at this point in the history
WASM Add Memory Pages
  • Loading branch information
bytemaster authored Sep 29, 2017
2 parents b2ba71f + 4e2b2ec commit 9b85e8c
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 9 deletions.
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();
}
}
}
}

0 comments on commit 9b85e8c

Please sign in to comment.