-
Notifications
You must be signed in to change notification settings - Fork 15
Default Environment
The inNative Default Environment is what implements memory.grow
and several inNative specific Helper Functions. Very little platform-specific code is actually generated by the compiler itself. Instead, most platform-specific code is concentrated here, inside a traditional C environment. If you want to add support for a new platform to inNative, this is the project you need to edit.
innative-env
primarily consists of a single internal.c
file that implements several basic functions, plus some assorted architecture-specific assembly files. The following critical functions must be implemented for all supported platforms:
extern void _innative_internal_env_memcpy(char* dest, const char* src, uint64_t sz);
This is an implementation of memcpy
, which usually doesn't need to be overridden on a platform-specific basis, but must still be provided. This function is called by inNative to fill linear memories with data from data
sections, and to fill tables with initial values from elem
sections. The existing implementation is a platform-agnostic, simple C implementation.
extern void* _innative_internal_env_grow_memory(void* p, uint64_t i, uint64_t max)
This is the function called by inNative to implement a memory.grow
function. This function must behave in a very precise manner to satisfy the assumptions made by the inNative compiler.
void* p
represents an existing memory pointer, if it exists. The function does not track or care about linear memory indices, it only cares about whether or not an allocation already exists. If p
is null, a new allocation should be returned. If p
is not null, a reallocation must happen, such that either p
itself is extended, or p
is freed and a new pointer is returned. If a new pointer is returned, you must ensure that p
was freed, as if this were an implementation of realloc()
. Note that the old pointer you are given will be the same as the pointer that was returned from the last invocation of this function, meaning it will be offset by 8 bytes. See below for the reason why.
uint64_t i
is the amount to grow the allocation - it is not the new size, it is the amount to add to the old size to get the new size. If p
was NULL, you can assume the old size was 0.
uint64_t max
is the maximum possible size of this allocation. Even if a linear memory has no specified maximum size, this value will be set at either the maximum value of a 32-bit unsigned integer, or the maximum value of a 64-bit unsigned integer, depending on the memory size. The current implementation assumes a value of 0
implies infinity, but never actually passes zero into the function. If the new size (i
plus the old size) exceeds the value of max
, the function must abort and return 0.
When you are allocating memory, you must reserve exactly 8 bytes at the beginning to store the allocation size as an unsigned 64-bit integer. You must then return a pointer exactly 8 bytes past the beginning of your allocation (return ((uint64_t*)p) + 1
), such that ((uint64_t*)p)[-1]
will return the old size of your allocation. This means that when you recieve an old pointer, it will also be pointing 8 bytes past the beginning, so to properly reallocate or free it, you will need to subtract 8 bytes from the old pointer. The pointer that you return must be 8-byte aligned to satisfy alignment assumptions. If anything goes wrong, this function must return 0.
extern void _innative_internal_env_free_memory(void* p)
This function is called during cleanup for all linear memories, regardless of whether or not they were actually allocated. This means that p
can be NULL, so this function must do nothing if p
is NULL.
If p
is not NULL, then it should be freed. However, remember that this pointer will be offset by 8 bytes, so you will have to subtract 8 bytes from the pointer before freeing it. On some platforms, freeing memory requires knowing how large it is. Simply use ((uint64_t*)p)[-1]
to acquire the size of the allocation, and use ((uint64_t*)p) - 1
to acquire the correct pointer to pass to the free function.
extern void _innative_internal_env_exit(int status)
Unlike DLLs, programs cannot simply return when they exit, they have to call a specific kernel function that exits the process. This function is called by inNative after all other cleanup has ended, and should call the appropriate kernel function to immediately terminate the process. On Windows, this is ExitProcess(status)
, and on Linux, it is the EXIT
system call. The status
parameter represents the return code that should be used, but it is almost always 0, because WebAssembly does not define an exit code.
extern IN_COMPILER_NAKED void* _innative_syscall(size_t syscall_number, const void* p1, size_t p2, size_t p3, size_t p4, size_t p5, size_t p6)
On Linux only, this is used for all system calls to the kernel, and is also considered a Helper Function. For POSIX architectures, this should execute a naked syscall with all 6 arguments using the syscall_number, which will probably require shuffling around registers. This is never called by inNative itself, but is critical for most POSIX systems.