forked from Mirrors/wasm3
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
426 lines
10 KiB
C
426 lines
10 KiB
C
//
|
|
// m3_exec.c
|
|
//
|
|
// Created by Steven Massey on 4/17/19.
|
|
// Copyright © 2019 Steven Massey. All rights reserved.
|
|
//
|
|
|
|
#include "m3_env.h"
|
|
#include "m3_exec.h"
|
|
#include "m3_compile.h"
|
|
|
|
|
|
static inline
|
|
IM3Memory GetMemoryInfo (M3MemoryHeader * header)
|
|
{
|
|
IM3Memory memory = & header->runtime->memory;
|
|
|
|
return memory;
|
|
}
|
|
|
|
static inline
|
|
IM3Runtime GetRuntime (M3MemoryHeader * header)
|
|
{
|
|
return header->runtime;
|
|
}
|
|
|
|
void ReportError2 (IM3Function i_function, m3ret_t i_result)
|
|
{
|
|
i_function->module->runtime->runtimeError = (M3Result)i_result;
|
|
}
|
|
|
|
|
|
d_m3OpDef (Call)
|
|
{
|
|
pc_t callPC = immediate (pc_t);
|
|
i32 stackOffset = immediate (i32);
|
|
IM3Memory memory = GetMemoryInfo (_mem);
|
|
|
|
m3stack_t sp = _sp + stackOffset;
|
|
|
|
m3ret_t r = Call (callPC, sp, _mem, d_m3OpDefaultArgs);
|
|
|
|
if (r == 0)
|
|
{
|
|
_mem = memory->mallocated;
|
|
return nextOp ();
|
|
}
|
|
else return r;
|
|
}
|
|
|
|
|
|
d_m3OpDef (CallIndirect)
|
|
{
|
|
IM3Module module = immediate (IM3Module);
|
|
IM3FuncType type = immediate (IM3FuncType);
|
|
i32 stackOffset = immediate (i32);
|
|
IM3Memory memory = GetMemoryInfo (_mem);
|
|
|
|
m3stack_t sp = _sp + stackOffset;
|
|
|
|
i32 tableIndex = * (i32 *) (sp + type->numArgs);
|
|
|
|
if (tableIndex >= 0 and (u32)tableIndex < module->table0Size)
|
|
{
|
|
m3ret_t r = m3Err_none;
|
|
|
|
IM3Function function = module->table0 [tableIndex];
|
|
|
|
if (function)
|
|
{
|
|
// TODO: this can eventually be simplified. by using a shared set of unique M3FuncType objects in
|
|
// M3Environment, the compare can be reduced to a single pointer-compare operation
|
|
|
|
if (type->numArgs != function->funcType->numArgs)
|
|
{
|
|
return m3Err_trapIndirectCallTypeMismatch;
|
|
}
|
|
|
|
if (type->returnType != function->funcType->returnType)
|
|
{
|
|
return m3Err_trapIndirectCallTypeMismatch;
|
|
}
|
|
|
|
for (u32 argIndex = 0; argIndex < type->numArgs; ++argIndex)
|
|
{
|
|
if (type->argTypes[argIndex] != function->funcType->argTypes[argIndex])
|
|
{
|
|
return m3Err_trapIndirectCallTypeMismatch;
|
|
}
|
|
}
|
|
|
|
if (not function->compiled)
|
|
r = Compile_Function (function);
|
|
|
|
if (not r)
|
|
{
|
|
r = Call (function->compiled, sp, _mem, d_m3OpDefaultArgs);
|
|
|
|
if (not r)
|
|
{
|
|
_mem = memory->mallocated;
|
|
r = nextOp ();
|
|
}
|
|
}
|
|
}
|
|
else r = "trap: table element is null";
|
|
|
|
return r;
|
|
}
|
|
else return m3Err_trapTableIndexOutOfRange;
|
|
}
|
|
|
|
|
|
d_m3OpDef (CallRawFunction)
|
|
{
|
|
M3RawCall call = (M3RawCall) (* _pc++);
|
|
IM3Runtime runtime = GetRuntime (_mem);
|
|
|
|
m3ret_t possible_trap = call (runtime, _sp, m3MemData(_mem));
|
|
return possible_trap;
|
|
}
|
|
|
|
|
|
d_m3OpDef (MemCurrent)
|
|
{
|
|
// TODO: get memory from _mem, so that compiled code isn't tied to a specific runtime
|
|
|
|
IM3Memory memory = GetMemoryInfo (_mem);
|
|
|
|
_r0 = memory->numPages;
|
|
|
|
return nextOp ();
|
|
}
|
|
|
|
|
|
d_m3OpDef (MemGrow)
|
|
{
|
|
IM3Runtime runtime = GetRuntime (_mem);
|
|
IM3Memory memory = & runtime->memory;
|
|
|
|
u32 numPagesToGrow = (u32) _r0;
|
|
_r0 = memory->numPages;
|
|
|
|
if (numPagesToGrow)
|
|
{
|
|
u32 requiredPages = memory->numPages + numPagesToGrow;
|
|
|
|
M3Result r = ResizeMemory (runtime, requiredPages);
|
|
if (r)
|
|
_r0 = -1;
|
|
|
|
_mem = memory->mallocated;
|
|
}
|
|
|
|
return nextOp ();
|
|
}
|
|
|
|
|
|
// it's a debate: should the compilation be trigger be the caller or callee page.
|
|
// it's a much easier to put it in the caller pager. if it's in the callee, either the entire page
|
|
// has be left dangling or it's just a stub that jumps to a newly acquire page. In Gestalt, I opted
|
|
// for the stub approach. Stubbing makes it easier to dynamically free the compilation. You can also
|
|
// do both.
|
|
d_m3OpDef (Compile)
|
|
{
|
|
rewrite_op (op_Call);
|
|
|
|
IM3Function function = immediate (IM3Function);
|
|
|
|
m3ret_t result = m3Err_none;
|
|
|
|
if (not function->compiled) // check to see if function was compiled since this operation was emitted.
|
|
result = Compile_Function (function);
|
|
|
|
if (not result)
|
|
{
|
|
// patch up compiled pc and call rewriten op_Call
|
|
*((size_t *) --_pc) = (size_t) (function->compiled);
|
|
--_pc;
|
|
result = nextOp ();
|
|
}
|
|
else ReportError2 (function, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
d_m3OpDef (Entry)
|
|
{
|
|
IM3Function function = immediate (IM3Function);
|
|
|
|
#if defined(d_m3SkipStackCheck)
|
|
if (true)
|
|
#else
|
|
if ((void*)(_sp + function->maxStackSlots) < _mem->maxStack)
|
|
#endif
|
|
{
|
|
function->hits++; m3log (exec, " enter %p > %s %s", _pc - 2, function->name ? function->name : ".unnamed", SPrintFunctionArgList (function, _sp));
|
|
|
|
u32 numLocals = function->numLocals;
|
|
|
|
m3stack_t stack = _sp + GetFunctionNumArgs (function);
|
|
while (numLocals--) // it seems locals need to init to zero (at least for optimized Wasm code) TODO: see if this is still true.
|
|
* (stack++) = 0;
|
|
|
|
if (function->constants) {
|
|
memcpy (stack, function->constants, function->numConstants * sizeof (u64));
|
|
}
|
|
|
|
m3ret_t r = nextOp ();
|
|
|
|
# if d_m3LogExec
|
|
u8 returnType = function->funcType->returnType;
|
|
|
|
char str [100] = { '!', 0 };
|
|
|
|
if (not r)
|
|
SPrintArg (str, 99, _sp, function->funcType->returnType);
|
|
|
|
m3log (exec, " exit < %s %s %s %s", function->name, returnType ? "->" : "", str, r ? r : "");
|
|
# elif d_m3LogStackTrace
|
|
if (r)
|
|
printf (" ** %s %p\n", function->name, _sp);
|
|
# endif
|
|
|
|
return r;
|
|
}
|
|
else return m3Err_trapStackOverflow;
|
|
}
|
|
|
|
|
|
d_m3OpDef (GetGlobal)
|
|
{
|
|
i64 * global = immediate (i64 *);
|
|
slot (i64) = * global; // printf ("get global: %p %" PRIi64 "\n", global, *global);
|
|
|
|
return nextOp ();
|
|
}
|
|
|
|
|
|
d_m3OpDef (SetGlobal_i)
|
|
{
|
|
i64 * global = immediate (i64 *);
|
|
* global = _r0; // printf ("set global: %p %" PRIi64 "\n", global, _r0);
|
|
|
|
return nextOp ();
|
|
}
|
|
|
|
|
|
|
|
d_m3OpDef (Loop)
|
|
{
|
|
m3ret_t r;
|
|
|
|
IM3Memory memory = GetMemoryInfo (_mem);
|
|
|
|
do
|
|
{
|
|
// linear memory pointer needs refreshed here because the block it's loop over
|
|
// can potentially invoke the grow operator.
|
|
r = nextOp (); // printf ("loop: %p\n", r);
|
|
_mem = memory->mallocated;
|
|
}
|
|
while (r == _pc);
|
|
|
|
return r;
|
|
}
|
|
|
|
|
|
d_m3OpDef (If_r)
|
|
{
|
|
i32 condition = (i32) _r0;
|
|
|
|
skip_immediate (pc_t); // empty preservation chain
|
|
|
|
pc_t elsePC = immediate (pc_t);
|
|
|
|
if (condition)
|
|
return nextOp ();
|
|
else
|
|
return jumpOp (elsePC);
|
|
}
|
|
|
|
|
|
d_m3OpDef (If_s)
|
|
{
|
|
i32 condition = slot (i32);
|
|
|
|
skip_immediate (pc_t); // empty preservation chain
|
|
|
|
pc_t elsePC = immediate (pc_t);
|
|
|
|
if (condition)
|
|
return nextOp ();
|
|
else
|
|
return jumpOp (elsePC);
|
|
}
|
|
|
|
|
|
d_m3OpDef (IfPreserve)
|
|
{
|
|
i32 condition = (i32) _r0;
|
|
|
|
pc_t p = immediate (pc_t);
|
|
jumpOp (p);
|
|
|
|
pc_t elsePC = immediate (pc_t); //printf ("else: %p\n", elsePC);
|
|
|
|
if (condition)
|
|
return nextOp ();
|
|
else
|
|
return jumpOp (elsePC);
|
|
}
|
|
|
|
|
|
d_m3OpDef (BranchTable)
|
|
{
|
|
i32 branchIndex = slot (i32); // branch index is always in a slot
|
|
i32 numTargets = immediate (i32);
|
|
|
|
pc_t * branches = (pc_t *) _pc;
|
|
|
|
if (branchIndex < 0 or branchIndex > numTargets)
|
|
branchIndex = numTargets; // the default index
|
|
|
|
return jumpOp (branches [branchIndex]);
|
|
}
|
|
|
|
|
|
|
|
d_m3OpDef (CopySlot_32)
|
|
{
|
|
u32 * dst = slot_ptr (u32);
|
|
u32 * src = slot_ptr (u32);
|
|
|
|
* dst = * src;
|
|
|
|
return nextOp ();
|
|
}
|
|
|
|
|
|
d_m3OpDef (CopySlot_64)
|
|
{
|
|
u64 * dst = slot_ptr (u64);
|
|
u64 * src = slot_ptr (u64);
|
|
|
|
* dst = * src; // printf ("copy: %p <- %" PRIi64 " <- %p\n", dst, * dst, src);
|
|
|
|
return nextOp ();
|
|
}
|
|
|
|
|
|
|
|
#if d_m3RuntimeStackDumps
|
|
//--------------------------------------------------------------------------------------------------------
|
|
d_m3OpDef (DumpStack)
|
|
{
|
|
u32 opcodeIndex = immediate (u32);
|
|
u64 stackHeight = immediate (u64);
|
|
IM3Function function = immediate (IM3Function);
|
|
|
|
cstr_t funcName = (function) ? function->name : "";
|
|
|
|
printf (" %4d ", opcodeIndex);
|
|
printf (" %-25s r0: 0x%016" PRIx64 " i:%" PRIi64 " u:%" PRIu64 "\n", funcName, _r0, _r0, _r0);
|
|
printf (" fp0: %lf \n", _fp0);
|
|
|
|
u64 * sp = _sp;
|
|
|
|
for (u32 i = 0; i < stackHeight; ++i)
|
|
{
|
|
printf ("%016llx ", (u64) sp);
|
|
|
|
cstr_t kind = "";
|
|
|
|
printf ("%5s %2d: 0x%" PRIx64 " %" PRIi64 "\n", kind, i, (u64) *(sp), (i64) *sp);
|
|
|
|
++sp;
|
|
}
|
|
printf ("---------------------------------------------------------------------------------------------------------\n");
|
|
|
|
return nextOpDirect();
|
|
}
|
|
#endif
|
|
|
|
|
|
# if d_m3EnableOpProfiling
|
|
//--------------------------------------------------------------------------------------------------------
|
|
M3ProfilerSlot s_opProfilerCounts [c_m3ProfilerSlotMask] = {};
|
|
|
|
void ProfileHit (cstr_t i_operationName)
|
|
{
|
|
u64 ptr = (u64) i_operationName;
|
|
|
|
M3ProfilerSlot * slot = & s_opProfilerCounts [ptr & c_m3ProfilerSlotMask];
|
|
|
|
if (slot->opName)
|
|
{
|
|
if (slot->opName != i_operationName)
|
|
{
|
|
m3Abort ("profiler slot collision; increase c_m3ProfilerSlotMask");
|
|
}
|
|
}
|
|
|
|
slot->opName = i_operationName;
|
|
slot->hitCount++;
|
|
}
|
|
|
|
void m3_PrintProfilerInfo ()
|
|
{
|
|
for (u32 i = 0; i <= c_m3ProfilerSlotMask; ++i)
|
|
{
|
|
M3ProfilerSlot * slot = & s_opProfilerCounts [i];
|
|
|
|
if (slot->opName)
|
|
printf ("%13llu %s\n", slot->hitCount, slot->opName);
|
|
}
|
|
}
|
|
|
|
# else
|
|
|
|
void m3_PrintProfilerInfo () {}
|
|
|
|
# endif
|