616 lines
13 KiB
C
616 lines
13 KiB
C
//
|
|
// m3_core.c
|
|
//
|
|
// Created by Steven Massey on 4/15/19.
|
|
// Copyright © 2019 Steven Massey. All rights reserved.
|
|
//
|
|
|
|
#define M3_IMPLEMENT_ERROR_STRINGS
|
|
#include "m3_config.h"
|
|
#include "wasm3.h"
|
|
|
|
#include "m3_core.h"
|
|
#include "m3_env.h"
|
|
|
|
void m3_Abort(const char* message) {
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "Error: %s\n", message);
|
|
#endif
|
|
abort();
|
|
}
|
|
|
|
M3_WEAK
|
|
M3Result m3_Yield ()
|
|
{
|
|
return m3Err_none;
|
|
}
|
|
|
|
#if d_m3LogTimestamps
|
|
|
|
#include <time.h>
|
|
|
|
#define SEC_TO_US(sec) ((sec)*1000000)
|
|
#define NS_TO_US(ns) ((ns)/1000)
|
|
|
|
static uint64_t initial_ts = -1;
|
|
|
|
uint64_t m3_GetTimestamp()
|
|
{
|
|
if (initial_ts == -1) {
|
|
initial_ts = 0;
|
|
initial_ts = m3_GetTimestamp();
|
|
}
|
|
struct timespec ts;
|
|
timespec_get(&ts, TIME_UTC);
|
|
uint64_t us = SEC_TO_US((uint64_t)ts.tv_sec) + NS_TO_US((uint64_t)ts.tv_nsec);
|
|
return us - initial_ts;
|
|
}
|
|
|
|
#endif
|
|
|
|
#if d_m3FixedHeap
|
|
|
|
static u8 fixedHeap[d_m3FixedHeap];
|
|
static u8* fixedHeapPtr = fixedHeap;
|
|
static u8* const fixedHeapEnd = fixedHeap + d_m3FixedHeap;
|
|
static u8* fixedHeapLast = NULL;
|
|
|
|
#if d_m3FixedHeapAlign > 1
|
|
# define HEAP_ALIGN_PTR(P) P = (u8*)(((size_t)(P)+(d_m3FixedHeapAlign-1)) & ~ (d_m3FixedHeapAlign-1));
|
|
#else
|
|
# define HEAP_ALIGN_PTR(P)
|
|
#endif
|
|
|
|
void * m3_Malloc_Impl (size_t i_size)
|
|
{
|
|
u8 * ptr = fixedHeapPtr;
|
|
|
|
fixedHeapPtr += i_size;
|
|
HEAP_ALIGN_PTR(fixedHeapPtr);
|
|
|
|
if (fixedHeapPtr >= fixedHeapEnd)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
memset (ptr, 0x0, i_size);
|
|
fixedHeapLast = ptr;
|
|
|
|
return ptr;
|
|
}
|
|
|
|
void m3_Free_Impl (void * i_ptr)
|
|
{
|
|
// Handle the last chunk
|
|
if (i_ptr && i_ptr == fixedHeapLast) {
|
|
fixedHeapPtr = fixedHeapLast;
|
|
fixedHeapLast = NULL;
|
|
} else {
|
|
//printf("== free %p [failed]\n", io_ptr);
|
|
}
|
|
}
|
|
|
|
void * m3_Realloc_Impl (void * i_ptr, size_t i_newSize, size_t i_oldSize)
|
|
{
|
|
if (M3_UNLIKELY(i_newSize == i_oldSize)) return i_ptr;
|
|
|
|
void * newPtr;
|
|
|
|
// Handle the last chunk
|
|
if (i_ptr && i_ptr == fixedHeapLast) {
|
|
fixedHeapPtr = fixedHeapLast + i_newSize;
|
|
HEAP_ALIGN_PTR(fixedHeapPtr);
|
|
if (fixedHeapPtr >= fixedHeapEnd)
|
|
{
|
|
return NULL;
|
|
}
|
|
newPtr = i_ptr;
|
|
} else {
|
|
newPtr = m3_Malloc_Impl(i_newSize);
|
|
if (!newPtr) {
|
|
return NULL;
|
|
}
|
|
if (i_ptr) {
|
|
memcpy(newPtr, i_ptr, i_oldSize);
|
|
}
|
|
}
|
|
|
|
if (i_newSize > i_oldSize) {
|
|
memset ((u8 *) newPtr + i_oldSize, 0x0, i_newSize - i_oldSize);
|
|
}
|
|
|
|
return newPtr;
|
|
}
|
|
|
|
#else
|
|
|
|
void * m3_Malloc_Impl (size_t i_size)
|
|
{
|
|
return calloc (i_size, 1);
|
|
}
|
|
|
|
void m3_Free_Impl (void * io_ptr)
|
|
{
|
|
free (io_ptr);
|
|
}
|
|
|
|
void * m3_Realloc_Impl (void * i_ptr, size_t i_newSize, size_t i_oldSize)
|
|
{
|
|
if (M3_UNLIKELY(i_newSize == i_oldSize)) return i_ptr;
|
|
|
|
void * newPtr = realloc (i_ptr, i_newSize);
|
|
|
|
if (M3_LIKELY(newPtr))
|
|
{
|
|
if (i_newSize > i_oldSize) {
|
|
memset ((u8 *) newPtr + i_oldSize, 0x0, i_newSize - i_oldSize);
|
|
}
|
|
return newPtr;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#endif
|
|
|
|
void * m3_CopyMem (const void * i_from, size_t i_size)
|
|
{
|
|
void * ptr = m3_Malloc("CopyMem", i_size);
|
|
if (ptr) {
|
|
memcpy (ptr, i_from, i_size);
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
|
|
#if d_m3LogNativeStack
|
|
|
|
static size_t stack_start;
|
|
static size_t stack_end;
|
|
|
|
void m3StackCheckInit ()
|
|
{
|
|
char stack;
|
|
stack_end = stack_start = (size_t)&stack;
|
|
}
|
|
|
|
void m3StackCheck ()
|
|
{
|
|
char stack;
|
|
size_t addr = (size_t)&stack;
|
|
|
|
size_t stackEnd = stack_end;
|
|
stack_end = M3_MIN (stack_end, addr);
|
|
|
|
// if (stackEnd != stack_end)
|
|
// printf ("maxStack: %ld\n", m3StackGetMax ());
|
|
}
|
|
|
|
int m3StackGetMax ()
|
|
{
|
|
return stack_start - stack_end;
|
|
}
|
|
|
|
#endif
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
|
|
M3Result NormalizeType (u8 * o_type, i8 i_convolutedWasmType)
|
|
{
|
|
M3Result result = m3Err_none;
|
|
|
|
u8 type = -i_convolutedWasmType;
|
|
|
|
if (type == 0x40)
|
|
type = c_m3Type_none;
|
|
else if (type < c_m3Type_i32 or type > c_m3Type_f64)
|
|
result = m3Err_invalidTypeId;
|
|
|
|
* o_type = type;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
bool IsFpType (u8 i_m3Type)
|
|
{
|
|
return (i_m3Type == c_m3Type_f32 or i_m3Type == c_m3Type_f64);
|
|
}
|
|
|
|
|
|
bool IsIntType (u8 i_m3Type)
|
|
{
|
|
return (i_m3Type == c_m3Type_i32 or i_m3Type == c_m3Type_i64);
|
|
}
|
|
|
|
|
|
bool Is64BitType (u8 i_m3Type)
|
|
{
|
|
if (i_m3Type == c_m3Type_i64 or i_m3Type == c_m3Type_f64)
|
|
return true;
|
|
else if (i_m3Type == c_m3Type_i32 or i_m3Type == c_m3Type_f32 or i_m3Type == c_m3Type_none)
|
|
return false;
|
|
else
|
|
return (sizeof (voidptr_t) == 8); // all other cases are pointers
|
|
}
|
|
|
|
u32 SizeOfType (u8 i_m3Type)
|
|
{
|
|
if (i_m3Type == c_m3Type_i32 or i_m3Type == c_m3Type_f32)
|
|
return sizeof (i32);
|
|
|
|
return sizeof (i64);
|
|
}
|
|
|
|
|
|
//-- Binary Wasm parsing utils ------------------------------------------------------------------------------------------
|
|
|
|
|
|
M3Result Read_u64 (u64 * o_value, bytes_t * io_bytes, cbytes_t i_end)
|
|
{
|
|
const u8 * ptr = * io_bytes;
|
|
ptr += sizeof (u64);
|
|
|
|
if (ptr <= i_end)
|
|
{
|
|
memcpy(o_value, * io_bytes, sizeof(u64));
|
|
M3_BSWAP_u64(*o_value);
|
|
* io_bytes = ptr;
|
|
return m3Err_none;
|
|
}
|
|
else return m3Err_wasmUnderrun;
|
|
}
|
|
|
|
|
|
M3Result Read_u32 (u32 * o_value, bytes_t * io_bytes, cbytes_t i_end)
|
|
{
|
|
const u8 * ptr = * io_bytes;
|
|
ptr += sizeof (u32);
|
|
|
|
if (ptr <= i_end)
|
|
{
|
|
memcpy(o_value, * io_bytes, sizeof(u32));
|
|
M3_BSWAP_u32(*o_value);
|
|
* io_bytes = ptr;
|
|
return m3Err_none;
|
|
}
|
|
else return m3Err_wasmUnderrun;
|
|
}
|
|
|
|
#if d_m3ImplementFloat
|
|
|
|
M3Result Read_f64 (f64 * o_value, bytes_t * io_bytes, cbytes_t i_end)
|
|
{
|
|
const u8 * ptr = * io_bytes;
|
|
ptr += sizeof (f64);
|
|
|
|
if (ptr <= i_end)
|
|
{
|
|
memcpy(o_value, * io_bytes, sizeof(f64));
|
|
M3_BSWAP_f64(*o_value);
|
|
* io_bytes = ptr;
|
|
return m3Err_none;
|
|
}
|
|
else return m3Err_wasmUnderrun;
|
|
}
|
|
|
|
|
|
M3Result Read_f32 (f32 * o_value, bytes_t * io_bytes, cbytes_t i_end)
|
|
{
|
|
const u8 * ptr = * io_bytes;
|
|
ptr += sizeof (f32);
|
|
|
|
if (ptr <= i_end)
|
|
{
|
|
memcpy(o_value, * io_bytes, sizeof(f32));
|
|
M3_BSWAP_f32(*o_value);
|
|
* io_bytes = ptr;
|
|
return m3Err_none;
|
|
}
|
|
else return m3Err_wasmUnderrun;
|
|
}
|
|
|
|
#endif
|
|
|
|
M3Result Read_u8 (u8 * o_value, bytes_t * io_bytes, cbytes_t i_end)
|
|
{
|
|
const u8 * ptr = * io_bytes;
|
|
|
|
if (ptr < i_end)
|
|
{
|
|
* o_value = * ptr;
|
|
* io_bytes = ptr + 1;
|
|
|
|
return m3Err_none;
|
|
}
|
|
else return m3Err_wasmUnderrun;
|
|
}
|
|
|
|
M3Result Read_opcode (m3opcode_t * o_value, bytes_t * io_bytes, cbytes_t i_end)
|
|
{
|
|
const u8 * ptr = * io_bytes;
|
|
|
|
if (ptr < i_end)
|
|
{
|
|
m3opcode_t opcode = * ptr++;
|
|
|
|
#if d_m3CascadedOpcodes == 0
|
|
if (M3_UNLIKELY(opcode == c_waOp_extended))
|
|
{
|
|
if (ptr < i_end)
|
|
{
|
|
opcode = (opcode << 8) | (* ptr++);
|
|
}
|
|
else return m3Err_wasmUnderrun;
|
|
}
|
|
#endif
|
|
* o_value = opcode;
|
|
* io_bytes = ptr;
|
|
|
|
return m3Err_none;
|
|
}
|
|
else return m3Err_wasmUnderrun;
|
|
}
|
|
|
|
|
|
M3Result ReadLebUnsigned (u64 * o_value, u32 i_maxNumBits, bytes_t * io_bytes, cbytes_t i_end)
|
|
{
|
|
M3Result result = m3Err_wasmUnderrun;
|
|
|
|
u64 value = 0;
|
|
|
|
u32 shift = 0;
|
|
const u8 * ptr = * io_bytes;
|
|
|
|
while (ptr < i_end)
|
|
{
|
|
u64 byte = * (ptr++);
|
|
|
|
value |= ((byte & 0x7f) << shift);
|
|
shift += 7;
|
|
|
|
if ((byte & 0x80) == 0)
|
|
{
|
|
result = m3Err_none;
|
|
break;
|
|
}
|
|
|
|
if (shift >= i_maxNumBits)
|
|
{
|
|
result = m3Err_lebOverflow;
|
|
break;
|
|
}
|
|
}
|
|
|
|
* o_value = value;
|
|
* io_bytes = ptr;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
M3Result ReadLebSigned (i64 * o_value, u32 i_maxNumBits, bytes_t * io_bytes, cbytes_t i_end)
|
|
{
|
|
M3Result result = m3Err_wasmUnderrun;
|
|
|
|
i64 value = 0;
|
|
|
|
u32 shift = 0;
|
|
const u8 * ptr = * io_bytes;
|
|
|
|
while (ptr < i_end)
|
|
{
|
|
u64 byte = * (ptr++);
|
|
|
|
value |= ((byte & 0x7f) << shift);
|
|
shift += 7;
|
|
|
|
if ((byte & 0x80) == 0)
|
|
{
|
|
result = m3Err_none;
|
|
|
|
if ((byte & 0x40) and (shift < 64)) // do sign extension
|
|
{
|
|
u64 extend = 0;
|
|
value |= (~extend << shift);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (shift >= i_maxNumBits)
|
|
{
|
|
result = m3Err_lebOverflow;
|
|
break;
|
|
}
|
|
}
|
|
|
|
* o_value = value;
|
|
* io_bytes = ptr;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
M3Result ReadLEB_u32 (u32 * o_value, bytes_t * io_bytes, cbytes_t i_end)
|
|
{
|
|
u64 value;
|
|
M3Result result = ReadLebUnsigned (& value, 32, io_bytes, i_end);
|
|
* o_value = (u32) value;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
M3Result ReadLEB_u7 (u8 * o_value, bytes_t * io_bytes, cbytes_t i_end)
|
|
{
|
|
u64 value;
|
|
M3Result result = ReadLebUnsigned (& value, 7, io_bytes, i_end);
|
|
* o_value = (u8) value;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
M3Result ReadLEB_i7 (i8 * o_value, bytes_t * io_bytes, cbytes_t i_end)
|
|
{
|
|
i64 value;
|
|
M3Result result = ReadLebSigned (& value, 7, io_bytes, i_end);
|
|
* o_value = (i8) value;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
M3Result ReadLEB_i32 (i32 * o_value, bytes_t * io_bytes, cbytes_t i_end)
|
|
{
|
|
i64 value;
|
|
M3Result result = ReadLebSigned (& value, 32, io_bytes, i_end);
|
|
* o_value = (i32) value;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
M3Result ReadLEB_i64 (i64 * o_value, bytes_t * io_bytes, cbytes_t i_end)
|
|
{
|
|
i64 value;
|
|
M3Result result = ReadLebSigned (& value, 64, io_bytes, i_end);
|
|
* o_value = value;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
M3Result Read_utf8 (cstr_t * o_utf8, bytes_t * io_bytes, cbytes_t i_end)
|
|
{
|
|
*o_utf8 = NULL;
|
|
|
|
u32 utf8Length;
|
|
M3Result result = ReadLEB_u32 (& utf8Length, io_bytes, i_end);
|
|
|
|
if (not result)
|
|
{
|
|
if (utf8Length <= d_m3MaxSaneUtf8Length)
|
|
{
|
|
const u8 * ptr = * io_bytes;
|
|
const u8 * end = ptr + utf8Length;
|
|
|
|
if (end <= i_end)
|
|
{
|
|
char * utf8 = (char *)m3_Malloc ("UTF8", utf8Length + 1);
|
|
|
|
if (utf8)
|
|
{
|
|
memcpy (utf8, ptr, utf8Length);
|
|
utf8 [utf8Length] = 0;
|
|
* o_utf8 = utf8;
|
|
}
|
|
|
|
* io_bytes = end;
|
|
}
|
|
else result = m3Err_wasmUnderrun;
|
|
}
|
|
else result = m3Err_missingUTF8;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#if d_m3RecordBacktraces
|
|
u32 FindModuleOffset (IM3Runtime i_runtime, pc_t i_pc)
|
|
{
|
|
// walk the code pages
|
|
IM3CodePage curr = i_runtime->pagesOpen;
|
|
bool pageFound = false;
|
|
|
|
while (curr)
|
|
{
|
|
if (ContainsPC (curr, i_pc))
|
|
{
|
|
pageFound = true;
|
|
break;
|
|
}
|
|
curr = curr->info.next;
|
|
}
|
|
|
|
if (!pageFound)
|
|
{
|
|
curr = i_runtime->pagesFull;
|
|
while (curr)
|
|
{
|
|
if (ContainsPC (curr, i_pc))
|
|
{
|
|
pageFound = true;
|
|
break;
|
|
}
|
|
curr = curr->info.next;
|
|
}
|
|
}
|
|
|
|
if (pageFound)
|
|
{
|
|
u32 result = 0;
|
|
|
|
bool pcFound = MapPCToOffset (curr, i_pc, & result);
|
|
d_m3Assert (pcFound);
|
|
|
|
return result;
|
|
}
|
|
else return 0;
|
|
}
|
|
|
|
|
|
void PushBacktraceFrame (IM3Runtime io_runtime, pc_t i_pc)
|
|
{
|
|
// don't try to push any more frames if we've already had an alloc failure
|
|
if (M3_UNLIKELY (io_runtime->backtrace.lastFrame == M3_BACKTRACE_TRUNCATED))
|
|
return;
|
|
|
|
M3BacktraceFrame * newFrame = m3_AllocStruct(M3BacktraceFrame);
|
|
|
|
if (!newFrame)
|
|
{
|
|
io_runtime->backtrace.lastFrame = M3_BACKTRACE_TRUNCATED;
|
|
return;
|
|
}
|
|
|
|
newFrame->moduleOffset = FindModuleOffset (io_runtime, i_pc);
|
|
|
|
if (!io_runtime->backtrace.frames || !io_runtime->backtrace.lastFrame)
|
|
io_runtime->backtrace.frames = newFrame;
|
|
else
|
|
io_runtime->backtrace.lastFrame->next = newFrame;
|
|
io_runtime->backtrace.lastFrame = newFrame;
|
|
}
|
|
|
|
|
|
void FillBacktraceFunctionInfo (IM3Runtime io_runtime, IM3Function i_function)
|
|
{
|
|
// If we've had an alloc failure then the last frame doesn't refer to the
|
|
// frame we want to fill in the function info for.
|
|
if (M3_UNLIKELY (io_runtime->backtrace.lastFrame == M3_BACKTRACE_TRUNCATED))
|
|
return;
|
|
|
|
if (!io_runtime->backtrace.lastFrame)
|
|
return;
|
|
|
|
io_runtime->backtrace.lastFrame->function = i_function;
|
|
}
|
|
|
|
|
|
void ClearBacktrace (IM3Runtime io_runtime)
|
|
{
|
|
M3BacktraceFrame * currentFrame = io_runtime->backtrace.frames;
|
|
while (currentFrame)
|
|
{
|
|
M3BacktraceFrame * nextFrame = currentFrame->next;
|
|
m3_Free (currentFrame);
|
|
currentFrame = nextFrame;
|
|
}
|
|
|
|
io_runtime->backtrace.frames = NULL;
|
|
io_runtime->backtrace.lastFrame = NULL;
|
|
}
|
|
#endif // d_m3RecordBacktraces
|