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

WASM Memory refactor to add more memory #490

Merged
merged 5 commits into from
Sep 26, 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
260 changes: 183 additions & 77 deletions contracts/eoslib/memory.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,79 +16,73 @@ namespace eos {
* @{
*/

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

friend void* malloc(uint32_t size);
friend void* realloc(void* ptr, uint32_t size);
friend void free(void* ptr);
public:
memory()
: _offset(0)
memory_manager()
: _current_heap(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;
// first pass of loop never has to initialize the slot in _available_heap
uint32_t needs_init = 0;
char* buffer = nullptr;
uint32_t current_heap = _current_heap;
for (;current_heap < HEAPS_SIZE; ++current_heap)
{
memory& current = _available_heaps[current_heap];
if(!current.is_init())
{
char* new_heap = nullptr;
if (current_heap == 0)
{
memset(_initial_heap, 0, sizeof(_initial_heap));
current.init(_initial_heap, INITIAL_HEAP_SIZE);
}
else
{
// REMOVE logic, just using to test out multiple heap logic till memory can be allocated
char* const new_heap = &_initial_heap[INITIAL_HEAP_SIZE + NEW_HEAP_SIZE * (current_heap - 1)];
_available_heaps[current_heap].init(new_heap, NEW_HEAP_SIZE);
}
}
buffer = current.malloc(size);
if (buffer != nullptr)
break;
}

// only update the current_heap if memory was allocated
if (buffer != nullptr)
{
_current_heap = current_heap;
}

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

void* realloc(void* ptr, uint32_t size)
{
char* realloc_ptr = nullptr;
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)
char* const char_ptr = static_cast<char*>(ptr);
for (memory* realloc_heap = _available_heaps; realloc_heap < _available_heaps + HEAPS_SIZE && realloc_heap->is_init(); ++realloc_heap)
{
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
if (realloc_heap->is_in_heap(char_ptr))
{
orig_ptr_size = 0;
realloc_ptr = realloc_heap->realloc_in_place(char_ptr, size, &orig_ptr_size);

if (realloc_ptr != nullptr)
return realloc_ptr;
else
break;
}
}
}
Expand All @@ -110,56 +104,168 @@ namespace eos {
// currently no-op
}

class buffer_ptr
class memory
{
public:
buffer_ptr(void* ptr)
: _ptr(static_cast<char*>(ptr))
, _size(*(uint32_t*)(static_cast<char*>(ptr) - SIZE_MARKER))
memory()
: _heap_size(0)
, _heap(nullptr)
, _offset(0)
{
}

buffer_ptr(void* ptr, uint32_t buff_size)
: _ptr(static_cast<char*>(ptr))
void init(char* const mem_heap, uint32_t size)
{
size(buff_size);
_heap_size = size;
_heap = mem_heap;
_offset = 0;
memset(_heap, 0, _heap_size);
}

const void* size_ptr()
uint32_t is_init() const
{
return _ptr - SIZE_MARKER;
return _heap != nullptr;
}

uint32_t size()
uint32_t is_in_heap(const char* const ptr) const
{
return _size;
const char* const END_OF_BUFFER = _heap + _heap_size;
const char* const FIRST_PTR_OF_BUFFER = _heap + SIZE_MARKER;
return ptr >= FIRST_PTR_OF_BUFFER && ptr < END_OF_BUFFER;
}

void size(uint32_t val)
uint32_t is_capacity_remaining() const
{
*reinterpret_cast<uint32_t*>(_ptr - SIZE_MARKER) = val;
_size = val;
return _offset + SIZE_MARKER < _heap_size;
}

char* end()
char* malloc(uint32_t size)
{
return _ptr + _size;
if (_offset + size + SIZE_MARKER > _heap_size || size == 0)
{
return nullptr;
}

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

char* realloc_in_place(char* const ptr, uint32_t size, uint32_t* orig_ptr_size)
{
const char* const END_OF_BUFFER = _heap + _heap_size;

buffer_ptr orig_buffer(ptr);
*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)
{
*orig_ptr_size = 0;
return nullptr;
}

if (ptr > END_OF_BUFFER - size)
{
// cannot resize in place
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 == &_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 == &_heap[_offset])
{
orig_buffer.size(size);
_offset += diff;

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

// could not resize in place
return nullptr;
}

char* ptr()
void free(char* )
{
return _ptr;
// currently no-op
}

private:
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;
};

char* const _ptr;
uint32_t _size;
uint32_t _heap_size;
char* _heap;
uint32_t _offset;
};

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;
static const uint32_t NEW_HEAP_SIZE = 1024; // should be 65536
static const uint32_t HEAPS_SIZE = 4; // _initial_heap plus 3 pages (64K each)
// should be just INITIAL_HEAP_SIZE, adding extra space for testing multiple buffers
char _initial_heap[INITIAL_HEAP_SIZE + NEW_HEAP_SIZE * (HEAPS_SIZE - 1)];
memory _available_heaps[HEAPS_SIZE];
uint32_t _current_heap;
} memory_heap;

/**
Expand Down
13 changes: 8 additions & 5 deletions libraries/chain/wasm_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -693,11 +693,14 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) {

char* memstart = &memoryRef<char>( current_memory, 0 );
// state.init_memory.resize(1<<16); /// TODO: actually get memory size
for( uint32_t i = 0; i < 10000; ++i )
if( memstart[i] ) {
state.mem_end = i+1;
//std::cerr << (char)memstart[i];
}
const auto allocated_memory = Runtime::getDefaultMemorySize(state.instance);
for( uint64_t i = 0; i < allocated_memory; ++i )
{
if( memstart[i] )
{
state.mem_end = i+1;
}
}
//ilog( "INIT MEMORY: ${size}", ("size", state.mem_end) );

state.init_memory.resize(state.mem_end);
Expand Down
1 change: 1 addition & 0 deletions libraries/wasm-jit/Include/Runtime/Runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ namespace Runtime

// Gets the default table/memory for a ModuleInstance.
RUNTIME_API MemoryInstance* getDefaultMemory(ModuleInstance* moduleInstance);
RUNTIME_API uint64_t getDefaultMemorySize(ModuleInstance* moduleInstance);
RUNTIME_API TableInstance* getDefaultTable(ModuleInstance* moduleInstance);

// Gets an object exported by a ModuleInstance by name.
Expand Down
1 change: 1 addition & 0 deletions libraries/wasm-jit/Source/Runtime/ModuleInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ namespace Runtime
}

MemoryInstance* getDefaultMemory(ModuleInstance* moduleInstance) { return moduleInstance->defaultMemory; }
uint64_t getDefaultMemorySize(ModuleInstance* moduleInstance) { return moduleInstance->defaultMemory->numPages << IR::numBytesPerPageLog2; }
TableInstance* getDefaultTable(ModuleInstance* moduleInstance) { return moduleInstance->defaultTable; }

ObjectInstance* getInstanceExport(ModuleInstance* moduleInstance,const std::string& name)
Expand Down
3 changes: 2 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ if(WASM_TOOLCHAIN)
add_dependencies(slow_test currency exchange)

add_subdirectory(api_tests/memory_test)
add_subdirectory(api_tests/extended_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 ${CMAKE_CURRENT_BINARY_DIR}/api_tests )
add_dependencies(api_test test_api memory_test)
add_dependencies(api_test test_api memory_test extended_memory_test)
endif()

configure_file(${CMAKE_CURRENT_SOURCE_DIR}/eosd_run_test.sh ${CMAKE_CURRENT_BINARY_DIR}/eosd_run_test.sh COPYONLY)
Loading