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

Initial Wasm memory allocation #400

Merged
merged 10 commits into from
Sep 12, 2017
51 changes: 51 additions & 0 deletions contracts/eoslib/memory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#pragma once

#include <eoslib/types.h>

extern "C" {
/**
* @defgroup memorycapi Memory C API
* @brief Defines common memory functions
* @ingroup TBD
*
* @{
*/

/**
* Copy a block of memory from source to destination.
* @brief Copy a block of memory from source to destination.
* @param destination Pointer to the destination to copy to.
* @param source Pointer to the source for copy from.
* @param num Number of bytes to copy.
*
* @return the destination pointer
*
* Example:
* @code
* char dest[6] = { 0 };
* char source[6] = { 'H', 'e', 'l', 'l', 'o', '\0' };
* memcpy(dest, source, 6 * sizeof(char));
* prints(dest); // Output: Hello
* @endcode
*/
void* memcpy( void* destination, const void* source, uint32_t num );

/**
* Fill block of memory.
* @brief Fill a block of memory with the provided value.
* @param ptr Pointer to memory to fill.
* @param value Value to set (it is passed as an int but converted to unsigned char).
* @param num Number of bytes to be set to the value.
*
* @return the destination pointer
*
* Example:
* @code
* char ptr[6] = { 'H', 'e', 'l', 'l', 'o', '\0' };
* memset(ptr, 'y', 6 * sizeof(char));
* prints(ptr); // Output: yyyyyy
* @endcode
*/
void* memset( void* ptr, uint32_t value, uint32_t num );
/// @}
} // extern "C"
215 changes: 215 additions & 0 deletions contracts/eoslib/memory.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
#pragma once

#include <eoslib/memory.h>
#include <eoslib/print.hpp>

namespace eos {

using ::memset;
using ::memcpy;

/**
* @defgroup memorycppapi Memory C++ API
* @brief Defines common memory functions
* @ingroup TBD
*
* @{
*/

class memory
{
friend void* malloc(uint32_t size);
friend void* realloc(void* ptr, uint32_t size);
friend void free(void* ptr);

public:
memory()
: _offset(0)
{
memset(_initial_heap, 0, sizeof(_initial_heap));
}

private:
void* malloc(uint32_t size)
{
if (_offset + size + SIZE_MARKER > INITIAL_HEAP_SIZE || size == 0)
return nullptr;

buffer_ptr new_buff(&_initial_heap[_offset + SIZE_MARKER], size);
_offset += size + SIZE_MARKER;
return new_buff.ptr();
}

void* realloc(void* ptr, uint32_t size)
{
uint32_t orig_ptr_size = 0;
const char* const END_OF_BUFFER = _initial_heap + INITIAL_HEAP_SIZE;
char* const char_ptr = static_cast<char*>(ptr);
if (ptr != nullptr)
{
buffer_ptr orig_buffer(ptr);
if (orig_buffer.size_ptr() >= _initial_heap && ptr < END_OF_BUFFER)
{
orig_ptr_size = orig_buffer.size();
// is the passed in pointer valid
char* const orig_buffer_end = orig_buffer.end();
if (orig_buffer_end < END_OF_BUFFER)
{
// is there enough memory to allocate new buffer
if (ptr >= END_OF_BUFFER - size)
{
// not handling in current implementation
return nullptr;
}

const int32_t diff = size - orig_ptr_size;
if (diff < 0)
{
memset(orig_buffer_end + diff, 0, -diff);
// if ptr was the last allocated buffer, we can contract
if (orig_buffer_end == &_initial_heap[_offset])
{
_offset += diff;
}
// else current implementation doesn't worry about freeing excess memory

return ptr;
}
// if ptr was the last allocated buffer, we can expand
else if (orig_buffer_end == &_initial_heap[_offset])
{
orig_buffer.size(size);
_offset += diff;

return ptr;
}
else if (diff == 0)
return ptr;
}
else
{
orig_ptr_size = 0;
}
}
}

char* new_alloc = static_cast<char*>(malloc(size));

const uint32_t copy_size = (size < orig_ptr_size) ? size : orig_ptr_size;
if (copy_size > 0)
{
memcpy(new_alloc, ptr, copy_size);
free (ptr);
}

return new_alloc;
}

void free(void* )
{
// currently no-op
}

class buffer_ptr
{
public:
buffer_ptr(void* ptr)
: _ptr(static_cast<char*>(ptr))
, _size(*(uint32_t*)(static_cast<char*>(ptr) - SIZE_MARKER))
{
}

buffer_ptr(void* ptr, uint32_t buff_size)
: _ptr(static_cast<char*>(ptr))
{
size(buff_size);
}

const void* size_ptr()
{
return _ptr - SIZE_MARKER;
}

uint32_t size()
{
return _size;
}

void size(uint32_t val)
{
*reinterpret_cast<uint32_t*>(_ptr - SIZE_MARKER) = val;
_size = val;
}

char* end()
{
return _ptr + _size;
}

char* ptr()
{
return _ptr;
}
private:

char* const _ptr;
uint32_t _size;
};

static const uint32_t SIZE_MARKER = sizeof(uint32_t);
static const uint32_t INITIAL_HEAP_SIZE = 8192;//32768;
char _initial_heap[INITIAL_HEAP_SIZE];
uint32_t _offset;
} memory_heap;

/**
* Allocate a block of memory.
* @brief Allocate a block of memory.
* @param size Size of memory block
*
* Example:
* @code
* uint64_t* int_buffer = malloc(500 * sizeof(uint64_t));
* @endcode
*/
inline void* malloc(uint32_t size)
{
return memory_heap.malloc(size);
}

/**
* Allocate a block of memory.
* @brief Allocate a block of memory.
* @param size Size of memory block
*
* Example:
* @code
* uint64_t* int_buffer = malloc(500 * sizeof(uint64_t));
* ...
* uint64_t* bigger_int_buffer = realloc(int_buffer, 600 * sizeof(uint64_t));
* @endcode
*/

inline void* realloc(void* ptr, uint32_t size)
{
return memory_heap.realloc(ptr, size);
}

/**
* Free a block of memory.
* @brief Free a block of memory.
* @param ptr Pointer to memory block to free.
*
* Example:
* @code
* uint64_t* int_buffer = malloc(500 * sizeof(uint64_t));
* ...
* free(int_buffer);
* @endcode
*/
inline void free(void* ptr)
{
return memory_heap.free(ptr);
}
/// @} /// mathcppapi
}
10 changes: 10 additions & 0 deletions libraries/chain/wasm_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,16 @@ DEFINE_INTRINSIC_FUNCTION3(env,memcpy,memcpy,i32,i32,dstp,i32,srcp,i32,len) {
return dstp;
}

DEFINE_INTRINSIC_FUNCTION3(env,memset,memset,i32,i32,rel_ptr,i32,value,i32,len) {
auto& wasm = wasm_interface::get();
auto mem = wasm.current_memory;
char* ptr = memoryArrayPtr<char>( mem, rel_ptr, len);
FC_ASSERT( len > 0 );

memset( ptr, value, len );
return rel_ptr;
}


/**
* Transaction C API implementation
Expand Down
5 changes: 3 additions & 2 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ if(WASM_TOOLCHAIN)
target_include_directories( slow_test PUBLIC ${CMAKE_BINARY_DIR}/contracts )
add_dependencies(slow_test currency exchange)

add_subdirectory(api_tests/memory_test)
file(GLOB API_TESTS "api_tests/*.cpp")
add_executable( api_test ${API_TESTS} ${COMMON_SOURCES} )
target_link_libraries( api_test eos_native_contract eos_chain chainbase eos_utilities eos_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} )
target_include_directories( api_test PUBLIC ${CMAKE_BINARY_DIR}/contracts ${CMAKE_SOURCE_DIR}/contracts )
add_dependencies(api_test test_api)
target_include_directories( api_test PUBLIC ${CMAKE_BINARY_DIR}/contracts ${CMAKE_SOURCE_DIR}/contracts ${CMAKE_CURRENT_BINARY_DIR}/api_tests )
add_dependencies(api_test test_api memory_test)
endif()

configure_file(${CMAKE_CURRENT_SOURCE_DIR}/eosd_run_test.sh ${CMAKE_CURRENT_BINARY_DIR}/eosd_run_test.sh COPYONLY)
83 changes: 83 additions & 0 deletions tests/api_tests/api_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@

#include <test_api/test_api.wast.hpp>
#include <test_api/test_api.hpp>

#include "memory_test/memory_test.wast.hpp"

FC_REFLECT( dummy_message, (a)(b)(c) );
FC_REFLECT( u128_msg, (values) );

Expand Down Expand Up @@ -408,5 +411,85 @@ BOOST_FIXTURE_TEST_CASE(test_all, testing_fixture)

} FC_LOG_AND_RETHROW() }

#define MEMORY_TEST_RUN(account_name) \
Make_Blockchain(chain); \
chain.produce_blocks(1); \
Make_Account(chain, account_name); \
chain.produce_blocks(1); \
\
\
types::setcode handler; \
handler.account = #account_name; \
\
auto wasm = assemble_wast( memory_test_wast ); \
handler.code.resize(wasm.size()); \
memcpy( handler.code.data(), wasm.data(), wasm.size() ); \
\
{ \
eos::chain::SignedTransaction trx; \
trx.scope = {#account_name}; \
trx.messages.resize(1); \
trx.messages[0].code = config::EosContractName; \
trx.messages[0].authorization.emplace_back(types::AccountPermission{#account_name,"active"}); \
transaction_set_message(trx, 0, "setcode", handler); \
trx.expiration = chain.head_block_time() + 100; \
transaction_set_reference_block(trx, chain.head_block_id()); \
chain.push_transaction(trx); \
chain.produce_blocks(1); \
} \
\
\
{ \
eos::chain::SignedTransaction trx; \
trx.scope = sort_names({#account_name,"inita"}); \
transaction_emplace_message(trx, #account_name, \
vector<types::AccountPermission>{}, \
"transfer", types::transfer{#account_name, "inita", 1,""}); \
trx.expiration = chain.head_block_time() + 100; \
transaction_set_reference_block(trx, chain.head_block_id()); \
chain.push_transaction(trx); \
chain.produce_blocks(1); \
}

#define MEMORY_TEST_CASE(test_case_name, account_name) \
BOOST_FIXTURE_TEST_CASE(test_case_name, testing_fixture) \
{ try{ \
MEMORY_TEST_RUN(account_name); \
} FC_LOG_AND_RETHROW() }

//Test wasm memory allocation
MEMORY_TEST_CASE(test_memory, testmemory)

//Test wasm memory allocation at boundaries
MEMORY_TEST_CASE(test_memory_bounds, testbounds)

//Test intrinsic provided memset and memcpy
MEMORY_TEST_CASE(test_memset_memcpy, testmemset)

//Test memcpy overlap at start of destination
BOOST_FIXTURE_TEST_CASE(test_memcpy_overlap_start, testing_fixture)
{
try {
MEMORY_TEST_RUN(testolstart);
BOOST_FAIL("memcpy should have thrown assert acception");
}
catch(fc::assert_exception& ex)
{
BOOST_REQUIRE(ex.to_detail_string().find("overlap of memory range is undefined") != std::string::npos);
}
}

//Test memcpy overlap at end of destination
BOOST_FIXTURE_TEST_CASE(test_memcpy_overlap_end, testing_fixture)
{
try {
MEMORY_TEST_RUN(testolend);
BOOST_FAIL("memcpy should have thrown assert acception");
}
catch(fc::assert_exception& ex)
{
BOOST_REQUIRE(ex.to_detail_string().find("overlap of memory range is undefined") != std::string::npos);
}
}

BOOST_AUTO_TEST_SUITE_END()
2 changes: 2 additions & 0 deletions tests/api_tests/memory_test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
file(GLOB SOURCE_FILES "*.cpp")
add_wast_target(memory_test "${SOURCE_FILES}" "${CMAKE_SOURCE_DIR}/contracts" ${CMAKE_CURRENT_BINARY_DIR})
Loading