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.
wasm3/source/m3_compile.c

2425 lines
81 KiB
C

//
// m3_compile.c
//
// Created by Steven Massey on 4/17/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#include "m3_env.h"
#include "m3_compile.h"
#include "m3_emit.h"
#include "m3_exec.h"
#include "m3_exception.h"
#include "m3_info.h"
//-------------------------------------------------------------------------------------------------------------------------
#define d_indent " | %s"
// just want less letter and numbers to stare at down the way in the compiler table
#define i_32 c_m3Type_i32
#define i_64 c_m3Type_i64
#define f_32 c_m3Type_f32
#define f_64 c_m3Type_f64
#define none c_m3Type_none
#define any (u8)-1
#if d_m3HasFloat
#define FPOP(x) x
#else
#define FPOP(x) NULL
#endif
static const IM3Operation c_preserveSetSlot [] = { NULL, op_PreserveSetSlot_i32, op_PreserveSetSlot_i64,
FPOP(op_PreserveSetSlot_f32), FPOP(op_PreserveSetSlot_f64) };
static const IM3Operation c_setSetOps [] = { NULL, op_SetSlot_i32, op_SetSlot_i64,
FPOP(op_SetSlot_f32), FPOP(op_SetSlot_f64) };
static const IM3Operation c_setGlobalOps [] = { NULL, op_SetGlobal_i32, op_SetGlobal_i64,
FPOP(op_SetGlobal_f32), FPOP(op_SetGlobal_f64) };
static const IM3Operation c_setRegisterOps [] = { NULL, op_SetRegister_i32, op_SetRegister_i64,
FPOP(op_SetRegister_f32), FPOP(op_SetRegister_f64) };
static const IM3Operation c_ifOps [2] [2] = { { op_i32_BranchIf_ss, op_i32_BranchIf_rs },
{ op_i64_BranchIf_ss, op_i64_BranchIf_rs } };
static const IM3Operation c_intSelectOps [2] [4] = { { op_Select_i32_rss, op_Select_i32_srs, op_Select_i32_ssr, op_Select_i32_sss },
{ op_Select_i64_rss, op_Select_i64_srs, op_Select_i64_ssr, op_Select_i64_sss } };
#if d_m3HasFloat
static const IM3Operation c_fpSelectOps [2] [2] [3] = { { { op_Select_f32_sss, op_Select_f32_srs, op_Select_f32_ssr }, // selector in slot
{ op_Select_f32_rss, op_Select_f32_rrs, op_Select_f32_rsr } }, // selector in reg
{ { op_Select_f64_sss, op_Select_f64_srs, op_Select_f64_ssr }, // selector in slot
{ op_Select_f64_rss, op_Select_f64_rrs, op_Select_f64_rsr } } }; // selector in reg
#endif
static const u16 c_m3RegisterUnallocated = 0;
static const u16 c_slotUnused = 0xffff;
M3Result AcquireCompilationCodePage (IM3Compilation o, IM3CodePage * o_codePage)
{
M3Result result = m3Err_none;
IM3CodePage page = AcquireCodePage (o->runtime);
if (page)
{
# if (d_m3EnableCodePageRefCounting)
{
if (o->function)
{
IM3Function func = o->function;
page->info.usageCount++;
u32 index = func->numCodePageRefs++;
_ (m3ReallocArray (& func->codePageRefs, IM3CodePage, func->numCodePageRefs, index));
func->codePageRefs [index] = page;
}
}
# endif
}
else _throw (m3Err_mallocFailedCodePage);
_catch:
* o_codePage = page;
return result;
}
void ReleaseCompilationCodePage (IM3Compilation o)
{
ReleaseCodePage (o->runtime, o->page);
}
bool IsStackPolymorphic (IM3Compilation o)
{
return o->block.isPolymorphic;
}
bool IsRegisterLocation (i16 i_location) { return (i_location >= d_m3Reg0SlotAlias); }
bool IsFpRegisterLocation (i16 i_location) { return (i_location == d_m3Fp0SlotAlias); }
bool IsIntRegisterLocation (i16 i_location) { return (i_location == d_m3Reg0SlotAlias); }
i16 GetNumBlockValues (IM3Compilation o) { return o->stackIndex - o->block.initStackIndex; }
u16 GetTypeNumSlots (u8 i_type)
{
# if d_m3Use32BitSlots
u16 n = Is64BitType (i_type) ? 2 : 1;
return n;
# else
return 1;
# endif
}
i16 GetStackTopIndex (IM3Compilation o)
{ d_m3Assert (o->stackIndex > 0 or IsStackPolymorphic (o));
return o->stackIndex - 1;
}
u8 GetStackTopTypeAtOffset (IM3Compilation o, u16 i_offset)
{
u8 type = c_m3Type_none;
++i_offset;
if (o->stackIndex >= i_offset)
type = o->typeStack [o->stackIndex - i_offset];
return type;
}
u8 GetStackTopType (IM3Compilation o)
{
return GetStackTopTypeAtOffset (o, 0);
}
u8 GetStackBottomType (IM3Compilation o, u16 i_offset)
{
u8 type = c_m3Type_none;
if (i_offset < o->stackIndex)
type = o->typeStack [i_offset];
return type;
}
bool IsStackIndexInRegister (IM3Compilation o, u16 i_stackIndex)
{ d_m3Assert (i_stackIndex < o->stackIndex or IsStackPolymorphic (o));
if (i_stackIndex < o->stackIndex)
return (o->wasmStack [i_stackIndex] >= d_m3Reg0SlotAlias);
else
return false;
}
bool IsStackTopIndexInRegister (IM3Compilation o, i16 i_stackTopOffset)
{ d_m3Assert (i_stackTopOffset >= 0 or IsStackPolymorphic (o));
if (i_stackTopOffset >= 0)
return IsStackIndexInRegister (o, (u16) i_stackTopOffset);
else
return false;
}
bool IsStackTopInRegister (IM3Compilation o)
{
return IsStackTopIndexInRegister (o, GetStackTopIndex (o));
}
bool IsStackTopMinus1InRegister (IM3Compilation o)
{
return IsStackTopIndexInRegister (o, GetStackTopIndex (o) - 1);
}
bool IsStackTopMinus2InRegister (IM3Compilation o)
{
return IsStackTopIndexInRegister (o, GetStackTopIndex (o) - 2);
}
bool IsStackTopInSlot (IM3Compilation o)
{
return not IsStackTopInRegister (o);
}
u16 GetStackTopSlotIndex (IM3Compilation o)
{
i16 i = GetStackTopIndex (o);
u16 slot = c_slotUnused;
if (i >= 0)
slot = o->wasmStack [i];
return slot;
}
u16 GetSlotForStackIndex (IM3Compilation o, u16 i_stackIndex)
{ d_m3Assert (i_stackIndex < o->stackIndex or IsStackPolymorphic (o));
u16 slot = c_slotUnused;
if (i_stackIndex < o->stackIndex)
slot = o->wasmStack [i_stackIndex];
return slot;
}
bool IsValidSlot (u16 i_slot)
{
return (i_slot < d_m3MaxFunctionSlots);
}
bool IsSlotAllocated (IM3Compilation o, u16 i_slot)
{
return o->m3Slots [i_slot];
}
void MarkSlotAllocated (IM3Compilation o, u16 i_slot)
{ d_m3Assert (o->m3Slots [i_slot] == 0); // shouldn't be already allocated
o->m3Slots [i_slot] = 1;
o->maxAllocatedSlotPlusOne = M3_MAX (o->maxAllocatedSlotPlusOne, i_slot + 1);
}
M3Result AllocateSlotsWithinRange (IM3Compilation o, u16 * o_slot, u8 i_type, u16 i_startSlot, u16 i_endSlot)
{
M3Result result = m3Err_functionStackOverflow;
u16 numSlots = GetTypeNumSlots (i_type);
u16 searchOffset = numSlots - 1;
if (d_m3Use32BitSlots) {
AlignSlotIndexToType (& i_startSlot, i_type);
}
// search for 1 or 2 consecutive slots in the execution stack
u16 i = i_startSlot;
while (i + searchOffset < i_endSlot)
{
if (o->m3Slots [i] == 0 and o->m3Slots [i + searchOffset] == 0)
{
MarkSlotAllocated (o, i);
if (numSlots == 2)
MarkSlotAllocated (o, i + 1);
* o_slot = i;
result = m3Err_none;
break;
}
// keep 2-slot allocations even-aligned
i += numSlots;
}
return result;
}
M3Result AllocateSlots (IM3Compilation o, u16 * o_slot, u8 i_type)
{
return AllocateSlotsWithinRange (o, o_slot, i_type, o->firstDynamicSlotIndex, d_m3MaxFunctionSlots);
}
M3Result AllocateConstantSlots (IM3Compilation o, u16 * o_slot, u8 i_type)
{
return AllocateSlotsWithinRange (o, o_slot, i_type, o->firstConstSlotIndex, o->firstDynamicSlotIndex);
}
M3Result IncrementSlotUsageCount (IM3Compilation o, u16 i_slot)
{ d_m3Assert (i_slot < d_m3MaxFunctionSlots);
M3Result result = m3Err_none; d_m3Assert (o->m3Slots [i_slot] > 0);
// OPTZ (memory): 'm3Slots' could still be fused with 'typeStack' if 4 bits were used to indicate: [0,1,2,many]. The many-case
// would scan 'wasmStack' to determine the actual usage count
if (o->m3Slots [i_slot] < 0xFF)
{
o->m3Slots [i_slot]++;
}
else result = "slot usage count overflow";
return result;
}
void DeallocateSlot (IM3Compilation o, i16 i_slotIndex, u8 i_type)
{ d_m3Assert (i_slotIndex >= o->firstDynamicSlotIndex);
d_m3Assert (o->m3Slots [i_slotIndex]);
for (u16 i = 0; i < GetTypeNumSlots (i_type); ++i, ++i_slotIndex)
{
-- o->m3Slots [i_slotIndex];
}
}
bool IsRegisterAllocated (IM3Compilation o, u32 i_register)
{
return (o->regStackIndexPlusOne [i_register] != c_m3RegisterUnallocated);
}
bool IsRegisterTypeAllocated (IM3Compilation o, u8 i_type)
{
return IsRegisterAllocated (o, IsFpType (i_type));
}
void AllocateRegister (IM3Compilation o, u32 i_register, u16 i_stackIndex)
{
d_m3Assert (not IsRegisterAllocated (o, i_register));
o->regStackIndexPlusOne [i_register] = i_stackIndex + 1;
}
void DeallocateRegister (IM3Compilation o, u32 i_register)
{
d_m3Assert (IsRegisterAllocated (o, i_register));
o->regStackIndexPlusOne [i_register] = c_m3RegisterUnallocated;
}
u16 GetRegisterStackIndex (IM3Compilation o, u32 i_register)
{
d_m3Assert (IsRegisterAllocated (o, i_register));
return o->regStackIndexPlusOne [i_register] - 1;
}
u16 GetMaxUsedSlotPlusOne (IM3Compilation o)
{
while (o->maxAllocatedSlotPlusOne > o->firstDynamicSlotIndex)
{
if (IsSlotAllocated (o, o->maxAllocatedSlotPlusOne - 1))
break;
o->maxAllocatedSlotPlusOne--;
}
return o->maxAllocatedSlotPlusOne;
}
M3Result PreserveRegisterIfOccupied (IM3Compilation o, u8 i_registerType)
{
M3Result result = m3Err_none;
u32 regSelect = IsFpType (i_registerType);
if (IsRegisterAllocated (o, regSelect))
{
u16 stackIndex = GetRegisterStackIndex (o, regSelect);
DeallocateRegister (o, regSelect);
u8 type = GetStackBottomType (o, stackIndex);
// and point to a exec slot
u16 slot = c_slotUnused;
_ (AllocateSlots (o, & slot, type));
o->wasmStack [stackIndex] = slot;
_ (EmitOp (o, c_setSetOps [type]));
EmitSlotOffset (o, slot);
}
_catch: return result;
}
// all values must be in slots befor entering loop, if, and else blocks
// otherwise they'd end up preserve-copied in the block to probably different locations (if/else)
M3Result PreserveRegisters (IM3Compilation o)
{
M3Result result;
_ (PreserveRegisterIfOccupied (o, c_m3Type_f64));
_ (PreserveRegisterIfOccupied (o, c_m3Type_i64));
_catch: return result;
}
M3Result PreserveNonTopRegisters (IM3Compilation o)
{
M3Result result = m3Err_none;
i16 stackTop = GetStackTopIndex (o);
if (stackTop >= 0)
{
if (IsRegisterAllocated (o, 0)) // r0
{
if (GetRegisterStackIndex (o, 0) != stackTop)
_ (PreserveRegisterIfOccupied (o, c_m3Type_i64));
}
if (IsRegisterAllocated (o, 1)) // fp0
{
if (GetRegisterStackIndex (o, 1) != stackTop)
_ (PreserveRegisterIfOccupied (o, c_m3Type_f64));
}
}
_catch: return result;
}
//----------------------------------------------------------------------------------------------------------------------
M3Result Push (IM3Compilation o, u8 i_type, u16 i_location)
{
M3Result result = m3Err_none;
#if !d_m3HasFloat
if (i_type == c_m3Type_f32 || i_type == c_m3Type_f64) {
return m3Err_unknownOpcode;
}
#endif
u16 stackIndex = o->stackIndex++; // printf ("push: %d\n", (i32) i);
if (stackIndex < d_m3MaxFunctionStackHeight)
{
o->wasmStack [stackIndex] = i_location;
o->typeStack [stackIndex] = i_type;
if (IsRegisterLocation (i_location))
{
u32 regSelect = IsFpRegisterLocation (i_location);
AllocateRegister (o, regSelect, stackIndex);
}
else
{
if (o->function)
{
// op_Entry uses this value to track and detect stack overflow
o->function->maxStackSlots = M3_MAX (o->function->maxStackSlots, i_location + 1);
}
}
if (d_m3LogWasmStack) dump_type_stack (o);
}
else result = m3Err_functionStackOverflow;
return result;
}
M3Result PushRegister (IM3Compilation o, u8 i_type)
{
u16 location = IsFpType (i_type) ? d_m3Fp0SlotAlias : d_m3Reg0SlotAlias; d_m3Assert (i_type or IsStackPolymorphic (o));
return Push (o, i_type, location);
}
M3Result Pop (IM3Compilation o)
{
M3Result result = m3Err_none;
if (o->stackIndex > o->block.initStackIndex)
{
o->stackIndex--; // printf ("pop: %d\n", (i32) o->stackIndex);
u16 slot = o->wasmStack [o->stackIndex];
u8 type = o->typeStack [o->stackIndex];
if (IsRegisterLocation (slot))
{
u32 regSelect = IsFpRegisterLocation (slot);
DeallocateRegister (o, regSelect);
}
else if (slot >= o->firstDynamicSlotIndex)
{
DeallocateSlot (o, slot, type);
}
}
else if (not IsStackPolymorphic (o))
result = m3Err_functionStackUnderrun;
return result;
}
M3Result UnwindBlockStack (IM3Compilation o)
{
M3Result result = m3Err_none;
i16 initStackIndex = o->block.initStackIndex;
u32 popCount = 0;
while (o->stackIndex > initStackIndex )
{
_ (Pop (o));
++popCount;
}
if (popCount)
m3log (compile, "unwound stack top: %d", popCount);
_catch: return result;
}
M3Result _PushAllocatedSlotAndEmit (IM3Compilation o, u8 i_type, bool i_doEmit)
{
M3Result result = m3Err_none;
u16 slot = c_slotUnused;
_ (AllocateSlots (o, & slot, i_type));
_ (Push (o, i_type, slot));
if (i_doEmit)
EmitSlotOffset (o, slot);
// printf ("push: %d\n", (u32) slot);
_catch: return result;
}
M3Result PushAllocatedSlotAndEmit (IM3Compilation o, u8 i_type)
{
return _PushAllocatedSlotAndEmit (o, i_type, true);
}
M3Result PushAllocatedSlot (IM3Compilation o, u8 i_type)
{
return _PushAllocatedSlotAndEmit (o, i_type, false);
}
M3Result PushConst (IM3Compilation o, u64 i_word, u8 i_type)
{
M3Result result = m3Err_none;
// Early-exit if we're not emitting
if (!o->page) return result;
bool matchFound = false;
bool is64BitType = Is64BitType(i_type);
u32 numUsedConstSlots = o->maxConstSlotIndex - o->firstConstSlotIndex;
u16 numRequiredSlots = GetTypeNumSlots (i_type);
// search for duplicate matching constant slot to reuse
if (numRequiredSlots == 2 and numUsedConstSlots >= 2)
{
u16 firstConstSlot = o->firstConstSlotIndex;
AlignSlotIndexToType (& firstConstSlot, c_m3Type_i64);
for (int slot = firstConstSlot; slot < o->maxConstSlotIndex - 1; slot += 2)
{
if (IsSlotAllocated (o, slot) and IsSlotAllocated (o, slot + 1))
{
u64 constant;
if (is64BitType) {
constant = * (u64 *) & o->constants [slot - o->firstConstSlotIndex];
} else {
constant = * (u32 *) & o->constants [slot - o->firstConstSlotIndex];
}
if (constant == i_word)
{
matchFound = true;
_ (Push (o, i_type, slot));
break;
}
}
}
}
else if (numRequiredSlots == 1)
{
for (u32 i = 0; i < numUsedConstSlots; ++i)
{
u16 slot = o->firstConstSlotIndex + i;
if (IsSlotAllocated (o, slot))
{
u64 constant;
if (is64BitType) {
constant = * (u64 *) & o->constants [i];
} else {
constant = * (u32 *) & o->constants [i];
}
if (constant == i_word)
{
matchFound = true;
_ (Push (o, i_type, slot));
break;
}
}
}
}
if (not matchFound)
{
u16 slot = c_slotUnused;
result = AllocateConstantSlots (o, & slot, i_type);
if (result) // no more constant table space; use inline constants
{
result = m3Err_none;
if (Is64BitType (i_type)) {
_ (EmitOp (o, op_Const64));
EmitWord64 (o->page, i_word);
} else {
_ (EmitOp (o, op_Const32));
EmitWord32 (o->page, i_word);
}
_ (PushAllocatedSlotAndEmit (o, i_type));
}
else
{
u16 constTableIndex = slot - o->firstConstSlotIndex;
if (is64BitType)
{
u64 * constant = (u64 *) & o->constants [constTableIndex];
* constant = i_word;
}
else
{
u32 * constant = (u32 *) & o->constants [constTableIndex];
* constant = i_word;
}
_ (Push (o, i_type, slot));
o->maxConstSlotIndex = M3_MAX (slot + numRequiredSlots, o->maxConstSlotIndex);
}
}
_catch: return result;
}
M3Result EmitTopSlotAndPop (IM3Compilation o)
{
if (IsStackTopInSlot (o))
EmitSlotOffset (o, GetStackTopSlotIndex (o));
return Pop (o);
}
// Or, maybe: EmitTrappingOp
M3Result AddTrapRecord (IM3Compilation o)
{
M3Result result = m3Err_none;
if (o->function)
{
}
return result;
}
M3Result AcquirePatch (IM3Compilation o, IM3BranchPatch * o_patch)
{
M3Result result = m3Err_none;
IM3BranchPatch patch = o->releasedPatches;
if (patch)
{
o->releasedPatches = patch->next;
patch->next = NULL;
}
else
_ (m3Alloc (& patch, M3BranchPatch, 1));
* o_patch = patch;
_catch: return result;
}
bool PatchBranches (IM3Compilation o)
{
bool didPatch = false;
M3CompilationScope * block = & o->block;
pc_t pc = GetPC (o);
IM3BranchPatch patches = block->patches;
IM3BranchPatch endPatch = patches;
while (patches)
{ m3log (compile, "patching location: %p to pc: %p", patches->location, pc);
if (not patches->location)
break;
* (patches->location) = pc;
endPatch = patches;
patches = patches->next;
}
if (block->patches)
{ d_m3Assert (endPatch->next == NULL);
// return patches to pool
endPatch->next = o->releasedPatches;
o->releasedPatches = block->patches;
block->patches = NULL;
didPatch = true;
}
return didPatch;
}
//-------------------------------------------------------------------------------------------------------------------------
M3Result CopyStackSlot (IM3Compilation o, u16 i_stackIndex, u16 i_destSlot)
{
M3Result result = m3Err_none;
IM3Operation op;
u8 type = GetStackBottomType (o, i_stackIndex);
bool inRegister = IsStackIndexInRegister (o, i_stackIndex);
if (inRegister)
{
op = c_setSetOps [type];
}
else op = Is64BitType (type) ? op_CopySlot_64 : op_CopySlot_32;
_ (EmitOp (o, op));
EmitSlotOffset (o, i_destSlot);
if (not inRegister)
{
u16 srcSlot = GetSlotForStackIndex (o, i_stackIndex);
EmitSlotOffset (o, srcSlot);
}
_catch: return result;
}
M3Result CopyTopSlot (IM3Compilation o, u16 i_destSlot)
{
M3Result result;
i16 stackTop = GetStackTopIndex (o);
_ (CopyStackSlot (o, (u16) stackTop, i_destSlot));
_catch: return result;
}
// a copy-on-write strategy is used with locals. when a get local occurs, it's not copied anywhere. the stack
// entry just has a index pointer to that local memory slot.
// then, when a previously referenced local is set, the current value needs to be preserved for those references
// TODO: consider getting rid of these specialized operations: PreserveSetSlot & PreserveCopySlot.
// They likely just take up space without improving performance.
M3Result PreservedCopyTopSlot (IM3Compilation o, u16 i_destSlot, u16 i_preserveSlot)
{
M3Result result = m3Err_none; d_m3Assert (i_destSlot != i_preserveSlot);
IM3Operation op;
u8 type = GetStackTopType (o);
if (IsStackTopInRegister (o))
{
op = c_preserveSetSlot [type];
}
else op = Is64BitType (type) ? op_PreserveCopySlot_64 : op_PreserveCopySlot_32;
_ (EmitOp (o, op));
EmitSlotOffset (o, i_destSlot);
if (IsStackTopInSlot (o))
EmitSlotOffset (o, GetStackTopSlotIndex (o));
EmitSlotOffset (o, i_preserveSlot);
_catch: return result;
}
M3Result MoveStackTopToRegister (IM3Compilation o)
{
M3Result result = m3Err_none;
if (IsStackTopInSlot (o))
{
u8 type = GetStackTopType (o);
_ (PreserveRegisterIfOccupied (o, type));
IM3Operation op = c_setRegisterOps [type];
_ (EmitOp (o, op));
_ (EmitTopSlotAndPop (o));
_ (PushRegister (o, type));
}
_catch: return result;
}
M3Result ReturnStackTop (IM3Compilation o)
{
M3Result result = m3Err_none;
i16 top = GetStackTopIndex (o);
if (top >= 0)
{
const u16 returnSlot = 0;
if (o->wasmStack [top] != returnSlot)
CopyTopSlot (o, returnSlot);
}
else if (not IsStackPolymorphic (o))
result = m3Err_functionStackUnderrun;
return result;
}
// if local is unreferenced, o_preservedSlotIndex will be equal to localIndex on return
M3Result FindReferencedLocalWithinCurrentBlock (IM3Compilation o, u16 * o_preservedSlotIndex, u32 i_localSlot)
{
M3Result result = m3Err_none;
IM3CompilationScope scope = & o->block;
i16 startIndex = scope->initStackIndex;
while (scope->opcode == c_waOp_block)
{
scope = scope->outer;
if (not scope)
break;
startIndex = scope->initStackIndex;
}
* o_preservedSlotIndex = (u16) i_localSlot;
for (u32 i = startIndex; i < o->stackIndex; ++i)
{
if (o->wasmStack [i] == i_localSlot)
{
if (* o_preservedSlotIndex == i_localSlot)
{
u8 localType = GetStackBottomType (o, i_localSlot);
_ (AllocateSlots (o, o_preservedSlotIndex, localType));
}
else
_ (IncrementSlotUsageCount (o, * o_preservedSlotIndex));
o->wasmStack [i] = * o_preservedSlotIndex;
}
}
_catch: return result;
}
M3Result GetBlockScope (IM3Compilation o, IM3CompilationScope * o_scope, i32 i_depth)
{
IM3CompilationScope scope = & o->block;
while (i_depth--)
{
scope = scope->outer;
if (not scope)
return "invalid block depth";
}
* o_scope = scope;
return m3Err_none;
}
//-------------------------------------------------------------------------------------------------------------------------
M3Result Compile_Const_i32 (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
i32 value;
_ (ReadLEB_i32 (& value, & o->wasm, o->wasmEnd));
_ (PushConst (o, value, c_m3Type_i32)); m3log (compile, d_indent " (const i32 = %" PRIi32 ")", get_indention_string (o), value);
_catch: return result;
}
M3Result Compile_Const_i64 (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
i64 value;
_ (ReadLEB_i64 (& value, & o->wasm, o->wasmEnd));
_ (PushConst (o, value, c_m3Type_i64)); m3log (compile, d_indent " (const i64 = %" PRIi64 ")", get_indention_string (o), value);
_catch: return result;
}
#if d_m3HasFloat
M3Result Compile_Const_f32 (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
union { u32 u; f32 f; } value = { 0 };
_ (Read_f32 (& value.f, & o->wasm, o->wasmEnd)); m3log (compile, d_indent " (const f32 = %" PRIf32 ")", get_indention_string (o), value.f);
_ (PushConst (o, value.u, c_m3Type_f32));
_catch: return result;
}
M3Result Compile_Const_f64 (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
union { u64 u; f64 f; } value = { 0 };
_ (Read_f64 (& value.f, & o->wasm, o->wasmEnd)); m3log (compile, d_indent " (const f64 = %" PRIf64 ")", get_indention_string (o), value.f);
_ (PushConst (o, value.u, c_m3Type_f64));
_catch: return result;
}
#endif
#ifdef d_m3CompileExtendedOpcode
M3Result Compile_ExtendedOpcode (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
i32 value;
u8 opcode;
_ (Read_u8 (& opcode, & o->wasm, o->wasmEnd)); m3log (compile, d_indent " (FC: %" PRIi32 ")", get_indention_string (o), opcode);
i_opcode = (i_opcode << 8) | opcode;
//printf("Extended opcode: 0x%x\n", i_opcode);
M3Compiler compiler = GetOpInfo(i_opcode)->compiler;
if (compiler)
result = (* compiler) (o, i_opcode);
else
result = m3Err_noCompiler;
o->previousOpcode = i_opcode;
_catch: return result;
}
#endif
M3Result Compile_Return (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
if (GetFunctionNumReturns (o->function))
{
_ (ReturnStackTop (o));
_ (Pop (o));
}
_ (EmitOp (o, op_Return));
o->block.isPolymorphic = true;
_catch: return result;
}
M3Result Compile_End (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result = m3Err_none;
// function end:
if (o->block.depth == 0)
{
u8 valueType = GetSingleRetType(o->block.type);
if (valueType)
{
// if there are branches to the function end, then their values are in a register
// if the block happens to have its top in a register too, then we can patch the branch
// to here. Otherwise, an ReturnStackTop is appended to the end of the function (at B) and
// branches patched there.
if (IsStackTopInRegister (o))
PatchBranches (o);
_ (ReturnStackTop (o));
}
else PatchBranches (o); // for no return type, branch to op_End
_ (EmitOp (o, op_Return));
_ (UnwindBlockStack (o));
// B: move register to return slot for branchehs
if (valueType)
{
if (PatchBranches (o))
{
_ (PushRegister (o, valueType));
ReturnStackTop (o);
_ (EmitOp (o, op_Return));
}
}
}
_catch: return result;
}
M3Result Compile_SetLocal (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
u32 localIndex;
_ (ReadLEB_u32 (& localIndex, & o->wasm, o->wasmEnd)); // printf ("--- set local: %d \n", localSlot);
if (localIndex < GetFunctionNumArgsAndLocals (o->function))
{
u16 localSlot = GetSlotForStackIndex (o, localIndex);
u16 preserveSlot;
_ (FindReferencedLocalWithinCurrentBlock (o, & preserveSlot, localSlot)); // preserve will be different than local, if referenced
if (preserveSlot == localSlot)
_ (CopyTopSlot (o, localSlot))
else
_ (PreservedCopyTopSlot (o, localSlot, preserveSlot))
if (i_opcode != c_waOp_teeLocal)
_ (Pop (o));
}
else _throw ("local index out of bounds");
_catch: return result;
}
M3Result Compile_GetLocal (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
_try {
u32 localIndex;
_ (ReadLEB_u32 (& localIndex, & o->wasm, o->wasmEnd));
if (localIndex >= GetFunctionNumArgsAndLocals (o->function))
_throw ("local index out of bounds");
u8 type = GetStackBottomType (o, localIndex);
u16 slot = GetSlotForStackIndex (o, localIndex);
_ (Push (o, type, slot));
} _catch: return result;
}
M3Result Compile_GetGlobal (IM3Compilation o, M3Global * i_global)
{
M3Result result;
IM3Operation op = Is64BitType (i_global->type) ? op_GetGlobal_s64 : op_GetGlobal_s32;
_ (EmitOp (o, op));
EmitPointer (o, & i_global->intValue);
_ (PushAllocatedSlotAndEmit (o, i_global->type));
_catch: return result;
}
M3Result Compile_SetGlobal (IM3Compilation o, M3Global * i_global)
{
M3Result result = m3Err_none;
if (i_global->isMutable)
{
IM3Operation op;
u8 type = GetStackTopType (o);
if (IsStackTopInRegister (o))
{
op = c_setGlobalOps [type];
}
else op = Is64BitType (type) ? op_SetGlobal_s64 : op_SetGlobal_s32;
_ (EmitOp (o, op));
EmitPointer (o, & i_global->intValue);
if (IsStackTopInSlot (o))
EmitSlotOffset (o, GetStackTopSlotIndex (o));
_ (Pop (o));
}
else result = m3Err_settingImmutableGlobal;
_catch: return result;
}
M3Result Compile_GetSetGlobal (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result = m3Err_none;
u32 globalIndex;
_ (ReadLEB_u32 (& globalIndex, & o->wasm, o->wasmEnd));
if (globalIndex < o->module->numGlobals)
{
if (o->module->globals)
{
M3Global * global = & o->module->globals [globalIndex];
result = (i_opcode == 0x23) ? Compile_GetGlobal (o, global) : Compile_SetGlobal (o, global);
}
else result = ErrorCompile (m3Err_globalMemoryNotAllocated, o, "module '%s' is missing global memory", o->module->name);
}
else result = m3Err_globaIndexOutOfBounds;
_catch: return result;
}
M3Result Compile_Branch (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
u32 depth;
_ (ReadLEB_u32 (& depth, & o->wasm, o->wasmEnd)); // printf ("depth: %d \n", depth);
IM3CompilationScope scope;
_ (GetBlockScope (o, & scope, depth));
IM3Operation op;
// branch target is a loop (continue)
if (scope->opcode == c_waOp_loop)
{
if (i_opcode == c_waOp_branchIf)
{
_ (MoveStackTopToRegister (o));
op = op_ContinueLoopIf;
_ (Pop (o));
}
else
{
op = op_ContinueLoop;
o->block.isPolymorphic = true;
}
_ (EmitOp (o, op));
EmitPointer (o, scope->pc);
}
else
{
u16 conditionSlot = c_slotUnused;
u16 valueSlot = c_slotUnused;
u8 valueType = GetSingleRetType(scope->type);
if (i_opcode == c_waOp_branchIf)
{
bool conditionInRegister = IsStackTopInRegister (o);
op = conditionInRegister ? op_BranchIf_r : op_BranchIf_s;
conditionSlot = GetStackTopSlotIndex (o);
_ (Pop (o)); // condition
// no Pop of values here 'cause the next statement in block can consume this value
if (IsFpType (valueType))
{
_ (MoveStackTopToRegister (o));
}
else if (IsIntType (valueType))
{
// need to deal with int value in slot. it needs to be copied to _r0 during the branch
if (IsStackTopInSlot (o))
{
valueSlot = GetStackTopSlotIndex (o);
op = c_ifOps [valueType - c_m3Type_i32] [conditionInRegister];
}
}
}
else
{
op = op_Branch;
if (valueType != c_m3Type_none and not IsStackPolymorphic (o))
_ (MoveStackTopToRegister (o));
o->block.isPolymorphic = true;
}
_ (EmitOp (o, op));
if (IsValidSlot (conditionSlot))
EmitSlotOffset (o, conditionSlot);
if (IsValidSlot (valueSlot))
EmitSlotOffset (o, valueSlot);
IM3BranchPatch patch;
_ (AcquirePatch (o, & patch));
patch->location = (pc_t *) ReservePointer (o);
patch->next = scope->patches;
scope->patches = patch;
}
_catch: return result;
}
M3Result Compile_BranchTable (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
_try {
u32 targetCount;
_ (ReadLEB_u32 (& targetCount, & o->wasm, o->wasmEnd));
_ (PreserveRegisterIfOccupied (o, c_m3Type_i64)); // move branch operand to a slot
u16 slot = GetStackTopSlotIndex (o);
_ (Pop (o));
// OPTZ: according to spec: "forward branches that target a control instruction with a non-empty
// result type consume matching operands first and push them back on the operand stack after unwinding"
// So, this move-to-reg is only necessary if the target scopes have a type.
if (GetNumBlockValues (o) > 0)
_ (MoveStackTopToRegister (o));
u32 numCodeLines = targetCount + 4; // 3 => IM3Operation + slot + target_count + default_target
_ (EnsureCodePageNumLines (o, numCodeLines));
_ (EmitOp (o, op_BranchTable));
EmitSlotOffset (o, slot);
EmitConstant32 (o, targetCount);
IM3CodePage continueOpPage = NULL;
++targetCount; // include default
for (u32 i = 0; i < targetCount; ++i)
{
u32 target;
_ (ReadLEB_u32 (& target, & o->wasm, o->wasmEnd));
IM3CompilationScope scope;
_ (GetBlockScope (o, & scope, target));
if (scope->opcode == c_waOp_loop)
{
// create a ContinueLoop operation on a fresh page
_ (AcquireCompilationCodePage (o, & continueOpPage));
pc_t startPC = GetPagePC (continueOpPage);
EmitPointer (o, startPC);
IM3CodePage savedPage = o->page;
o->page = continueOpPage;
_ (EmitOp (o, op_ContinueLoop));
EmitPointer (o, scope->pc);
ReleaseCompilationCodePage (o); // FIX: continueOpPage can get lost if thrown
o->page = savedPage;
}
else
{
IM3BranchPatch patch;
_ (AcquirePatch (o, & patch));
patch->location = (pc_t *) ReservePointer (o);
patch->next = scope->patches;
scope->patches = patch;
}
}
o->block.isPolymorphic = true;
}
_catch:
return result;
}
void AlignSlotIndexToType (u16 * io_slotIndex, u8 i_type)
{
// align 64-bit words to even slots
u16 numSlots = GetTypeNumSlots (i_type);
u16 mask = numSlots - 1;
* io_slotIndex = (* io_slotIndex + mask) & ~mask;
}
M3Result CompileCallArgsAndReturn (IM3Compilation o, u16 * o_stackOffset, IM3FuncType i_type, bool i_isIndirect)
{
M3Result result = m3Err_none;
_try {
u16 topSlot = GetMaxUsedSlotPlusOne (o);
// force use of at least one stack slot; this is to help ensure
// the m3 stack overflows (and traps) before the native stack can overflow.
// e.g. see Wasm spec test 'runaway' in call.wast
topSlot = M3_MAX (1, topSlot);
// stack frame is 64-bit aligned
AlignSlotIndexToType (& topSlot, c_m3Type_i64);
* o_stackOffset = topSlot;
// wait to pop this here so that topSlot search is correct
if (i_isIndirect)
_ (Pop (o));
u32 numArgs = i_type->numArgs;
u32 slotsPerArg = sizeof (u64) / sizeof (m3slot_t);
// args are 64-bit aligned
u16 argTop = topSlot + numArgs * slotsPerArg;
while (numArgs--)
{
_ (CopyTopSlot (o, argTop -= slotsPerArg));
_ (Pop (o));
}
if (i_type->numRets)
{
MarkSlotAllocated (o, topSlot);
_ (Push (o, GetSingleRetType(i_type), topSlot));
}
} _catch: return result;
}
M3Result Compile_Call (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
_try {
u32 functionIndex;
_ (ReadLEB_u32 (& functionIndex, & o->wasm, o->wasmEnd));
IM3Function function = Module_GetFunction (o->module, functionIndex);
if (function)
{ m3log (compile, d_indent " (func= '%s'; args= %d)",
get_indention_string (o), m3_GetFunctionName (function), function->funcType->numArgs);
if (function->module)
{
// OPTZ: could avoid arg copy when args are already sequential and at top
u16 slotTop;
_ (CompileCallArgsAndReturn (o, & slotTop, function->funcType, false));
IM3Operation op;
const void * operand;
if (function->compiled)
{
op = op_Call;
operand = function->compiled;
}
else
{ d_m3Assert (function->module); // not linked
op = op_Compile;
operand = function;
}
_ (EmitOp (o, op));
EmitPointer (o, operand);
EmitSlotOffset (o, slotTop);
}
else
{
result = ErrorCompile (m3Err_functionImportMissing, o, "'%s.%s'", GetFunctionImportModuleName (function), m3_GetFunctionName (function));
}
}
else result = m3Err_functionLookupFailed;
} _catch: return result;
}
M3Result Compile_CallIndirect (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
_try {
u32 typeIndex;
_ (ReadLEB_u32 (& typeIndex, & o->wasm, o->wasmEnd));
i8 reserved;
_ (ReadLEB_i7 (& reserved, & o->wasm, o->wasmEnd));
_throwif ("function type index out of range", typeIndex >= o->module->numFuncTypes);
if (IsStackTopInRegister (o))
_ (PreserveRegisterIfOccupied (o, c_m3Type_i32));
u16 tableIndexSlot = GetStackTopSlotIndex (o);
u16 execTop;
IM3FuncType type = o->module->funcTypes [typeIndex];
_ (CompileCallArgsAndReturn (o, & execTop, type, true));
_ (EmitOp (o, op_CallIndirect));
EmitSlotOffset (o, tableIndexSlot);
EmitPointer (o, o->module);
EmitPointer (o, type); // TODO: unify all types in M3Environment
EmitSlotOffset (o, execTop);
} _catch:
return result;
}
M3Result Compile_Memory_Current (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
i8 reserved;
_ (ReadLEB_i7 (& reserved, & o->wasm, o->wasmEnd));
_ (EmitOp (o, op_MemCurrent));
_ (PushRegister (o, c_m3Type_i32));
_catch: return result;
}
M3Result Compile_Memory_Grow (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
i8 reserved;
_ (ReadLEB_i7 (& reserved, & o->wasm, o->wasmEnd));
_ (MoveStackTopToRegister (o)); // a stack flavor of Grow would get rid of this
_ (Pop (o));
_ (EmitOp (o, op_MemGrow));
_ (PushRegister (o, c_m3Type_i32));
_catch: return result;
}
static
M3Result ReadBlockType (IM3Compilation o, IM3FuncType * o_blockType)
{
M3Result result;
i64 type;
_ (ReadLebSigned (& type, 33, & o->wasm, o->wasmEnd));
if (type < 0)
{
u8 valueType;
_ (NormalizeType (&valueType, type)); m3log (compile, d_indent " (type: %s)", get_indention_string (o), c_waTypes [valueType]);
*o_blockType = o->module->environment->retFuncTypes[valueType];
}
else
{
_throwif("func type out of bounds", type >= o->module->numFuncTypes);
*o_blockType = o->module->funcTypes[type]; m3log (compile, d_indent " (type: %s)", get_indention_string (o), SPrintFuncTypeSignature (*o_blockType));
}
_catch: return result;
}
// This preemptively preserves args and locals on the stack that might be written-to in the subsequent block
// (versus the COW strategy that happens in SetLocal within a block). Initially, I thought I'd have to be clever and
// retroactively insert preservation code to avoid impacting general performance, but this compilation pattern doesn't
// really occur in compiled Wasm code, so PreserveArgsAndLocals generally does nothing. Still waiting on a real-world case!
M3Result PreserveArgsAndLocals (IM3Compilation o)
{
M3Result result = m3Err_none;
if (o->stackIndex > o->firstDynamicStackIndex)
{
u32 numArgsAndLocals = GetFunctionNumArgsAndLocals (o->function);
for (u32 i = 0; i < numArgsAndLocals; ++i)
{
u16 localSlot = GetSlotForStackIndex (o, i);
u16 preservedSlotIndex;
_ (FindReferencedLocalWithinCurrentBlock (o, & preservedSlotIndex, localSlot));
if (preservedSlotIndex != localSlot)
{
u8 type = GetStackBottomType (o, i);
IM3Operation op = Is64BitType (type) ? op_CopySlot_64 : op_CopySlot_32;
EmitOp (o, op);
EmitSlotOffset (o, preservedSlotIndex);
EmitSlotOffset (o, localSlot);
}
}
}
_catch:
return result;
}
M3Result Compile_LoopOrBlock (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
_ (PreserveRegisters (o));
_ (PreserveArgsAndLocals (o));
IM3FuncType blockType;
_ (ReadBlockType (o, & blockType));
if (i_opcode == c_waOp_loop)
_ (EmitOp (o, op_Loop));
_ (CompileBlock (o, blockType, i_opcode));
_catch: return result;
}
M3Result CompileElseBlock (IM3Compilation o, pc_t * o_startPC, IM3FuncType i_blockType)
{
M3Result result;
_try {
IM3CodePage elsePage;
_ (AcquireCompilationCodePage (o, & elsePage));
* o_startPC = GetPagePC (elsePage);
IM3CodePage savedPage = o->page;
o->page = elsePage;
_ (CompileBlock (o, i_blockType, c_waOp_else));
_ (EmitOp (o, op_Branch));
EmitPointer (o, GetPagePC (savedPage));
ReleaseCompilationCodePage (o);
o->page = savedPage;
} _catch:
return result;
}
M3Result Compile_If (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
_try {
_ (PreserveNonTopRegisters (o));
_ (PreserveArgsAndLocals (o));
IM3Operation op = IsStackTopInRegister (o) ? op_If_r : op_If_s;
_ (EmitOp (o, op));
_ (EmitTopSlotAndPop (o));
i32 stackIndex = o->stackIndex;
pc_t * pc = (pc_t *) ReservePointer (o);
IM3FuncType blockType;
_ (ReadBlockType (o, & blockType));
_ (CompileBlock (o, blockType, i_opcode));
if (o->previousOpcode == c_waOp_else)
{
if (blockType and o->stackIndex > stackIndex)
{
_ (Pop (o));
}
_ (CompileElseBlock (o, pc, blockType));
}
else * pc = GetPC (o);
} _catch: return result;
}
M3Result Compile_Select (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result = m3Err_none;
u16 slots [3] = { c_slotUnused, c_slotUnused, c_slotUnused };
u8 type = GetStackTopTypeAtOffset (o, 1); // get type of selection
IM3Operation op = NULL;
if (IsFpType (type))
{
#if d_m3HasFloat
// not consuming a fp reg, so preserve
if (not IsStackTopMinus1InRegister (o) and
not IsStackTopMinus2InRegister (o))
{
_ (PreserveRegisterIfOccupied (o, type));
}
bool selectorInReg = IsStackTopInRegister (o);
slots [0] = GetStackTopSlotIndex (o);
_ (Pop (o));
u32 opIndex = 0;
for (u32 i = 1; i <= 2; ++i)
{
if (IsStackTopInRegister (o))
opIndex = i;
else
slots [i] = GetStackTopSlotIndex (o);
_ (Pop (o));
}
op = c_fpSelectOps [type - c_m3Type_f32] [selectorInReg] [opIndex];
#else
_throw (m3Err_unknownOpcode);
#endif
}
else if (IsIntType (type))
{
// 'sss' operation doesn't consume a register, so might have to protected its contents
if (not IsStackTopInRegister (o) and
not IsStackTopMinus1InRegister (o) and
not IsStackTopMinus2InRegister (o))
{
_ (PreserveRegisterIfOccupied (o, type));
}
u32 opIndex = 3; // op_Select_*_sss
for (u32 i = 0; i < 3; ++i)
{
if (IsStackTopInRegister (o))
opIndex = i;
else
slots [i] = GetStackTopSlotIndex (o);
_ (Pop (o));
}
op = c_intSelectOps [type - c_m3Type_i32] [opIndex];
}
else if (not IsStackPolymorphic (o))
_throw (m3Err_functionStackUnderrun);
EmitOp (o, op);
for (u32 i = 0; i < 3; i++)
{
if (IsValidSlot (slots [i]))
EmitSlotOffset (o, slots [i]);
}
_ (PushRegister (o, type));
_catch: return result;
}
M3Result Compile_Drop (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result = Pop (o); if (d_m3LogWasmStack) dump_type_stack (o);
return result;
}
M3Result Compile_Nop (IM3Compilation o, m3opcode_t i_opcode)
{
return m3Err_none;
}
M3Result Compile_Unreachable (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
_ (AddTrapRecord (o));
_ (EmitOp (o, op_Unreachable));
o->block.isPolymorphic = true;
_catch:
return result;
}
// TODO OPTZ: currently all stack slot indicies take up a full word, but
// dual stack source operands could be packed together
M3Result Compile_Operator (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
const M3OpInfo * op = GetOpInfo(i_opcode);
IM3Operation operation;
// This preserve is for for FP compare operations.
// either need additional slot destination operations or the
// easy fix, move _r0 out of the way.
// moving out the way might be the optimal solution most often?
// otherwise, the _r0 reg can get buried down in the stack
// and be idle & wasted for a moment.
if (IsFpType (GetStackTopType (o)) and IsIntType (op->type))
{
_ (PreserveRegisterIfOccupied (o, op->type));
}
if (op->stackOffset == 0)
{
if (IsStackTopInRegister (o))
{
operation = op->operations [0]; // _s
}
else
{
_ (PreserveRegisterIfOccupied (o, op->type));
operation = op->operations [1]; // _r
}
}
else
{
if (IsStackTopInRegister (o))
{
operation = op->operations [0]; // _rs
if (IsStackTopMinus1InRegister (o))
{ d_m3Assert (i_opcode == 0x38 or i_opcode == 0x39);
operation = op->operations [3]; // _rr for fp.store
}
}
else if (IsStackTopMinus1InRegister (o))
{
operation = op->operations [1]; // _sr
if (not operation) // must be commutative, then
operation = op->operations [0];
}
else
{
_ (PreserveRegisterIfOccupied (o, op->type)); // _ss
operation = op->operations [2];
}
}
if (operation)
{
_ (EmitOp (o, operation));
_ (EmitTopSlotAndPop (o));
if (op->stackOffset < 0)
_ (EmitTopSlotAndPop (o));
if (op->type != c_m3Type_none)
_ (PushRegister (o, op->type));
}
else
{
# ifdef DEBUG
result = ErrorCompile ("no operation found for opcode", o, "'%s'", op->name);
# else
result = ErrorCompile ("no operation found for opcode", o, "");
# endif
}
_catch: return result;
}
M3Result Compile_Convert (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result = m3Err_none;
const M3OpInfo * opInfo = GetOpInfo(i_opcode);
bool destInSlot = IsRegisterTypeAllocated (o, opInfo->type);
bool sourceInSlot = IsStackTopInSlot (o);
IM3Operation op = opInfo->operations [destInSlot * 2 + sourceInSlot];
_ (EmitOp (o, op));
_ (EmitTopSlotAndPop (o));
if (destInSlot)
_ (PushAllocatedSlotAndEmit (o, opInfo->type))
else
_ (PushRegister (o, opInfo->type))
_catch: return result;
}
M3Result Compile_Load_Store (IM3Compilation o, m3opcode_t i_opcode)
{
M3Result result;
_try {
u32 alignHint, memoryOffset;
_ (ReadLEB_u32 (& alignHint, & o->wasm, o->wasmEnd));
_ (ReadLEB_u32 (& memoryOffset, & o->wasm, o->wasmEnd));
m3log (compile, d_indent " (offset = %d)", get_indention_string (o), memoryOffset);
const M3OpInfo * op = GetOpInfo(i_opcode);
if (IsFpType (op->type))
_ (PreserveRegisterIfOccupied (o, c_m3Type_f64));
_ (Compile_Operator (o, i_opcode));
EmitConstant32 (o, memoryOffset);
}
_catch: return result;
}
// d_logOp, d_logOp2 macros aren't actually used by the compiler, just codepage decoding (d_m3LogCodePages = 1)
#define d_logOp(OP) { op_##OP, NULL, NULL, NULL }
#define d_logOp2(OP1,OP2) { op_##OP1, op_##OP2, NULL, NULL }
#define d_emptyOpList { NULL, NULL, NULL, NULL }
#define d_unaryOpList(TYPE, NAME) { op_##TYPE##_##NAME##_r, op_##TYPE##_##NAME##_s, NULL, NULL }
#define d_binOpList(TYPE, NAME) { op_##TYPE##_##NAME##_rs, op_##TYPE##_##NAME##_sr, op_##TYPE##_##NAME##_ss, NULL }
#define d_storeFpOpList(TYPE, NAME) { op_##TYPE##_##NAME##_rs, op_##TYPE##_##NAME##_sr, op_##TYPE##_##NAME##_ss, op_##TYPE##_##NAME##_rr }
#define d_commutativeBinOpList(TYPE, NAME) { op_##TYPE##_##NAME##_rs, NULL, op_##TYPE##_##NAME##_ss, NULL }
#define d_convertOpList(OP) { op_##OP##_r_r, op_##OP##_r_s, op_##OP##_s_r, op_##OP##_s_s }
const M3OpInfo c_operations [] =
{
M3OP( "unreachable", 0, none, d_logOp (Unreachable), Compile_Unreachable ), // 0x00
M3OP( "nop", 0, none, d_emptyOpList, Compile_Nop ), // 0x01 .
M3OP( "block", 0, none, d_emptyOpList, Compile_LoopOrBlock ), // 0x02
M3OP( "loop", 0, none, d_logOp (Loop), Compile_LoopOrBlock ), // 0x03
M3OP( "if", -1, none, d_emptyOpList, Compile_If ), // 0x04
M3OP( "else", 0, none, d_emptyOpList, Compile_Nop ), // 0x05
M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, // 0x06 - 0x0a
M3OP( "end", 0, none, d_emptyOpList, Compile_End ), // 0x0b
M3OP( "br", 0, none, d_logOp (Branch), Compile_Branch ), // 0x0c
M3OP( "br_if", -1, none, d_logOp2 (BranchIf_r, BranchIf_s), Compile_Branch ), // 0x0d
M3OP( "br_table", -1, none, d_logOp (BranchTable), Compile_BranchTable ), // 0x0e
M3OP( "return", 0, any, d_logOp (Return), Compile_Return ), // 0x0f
M3OP( "call", 0, any, d_logOp (Call), Compile_Call ), // 0x10
M3OP( "call_indirect", 0, any, d_logOp (CallIndirect), Compile_CallIndirect ), // 0x11
M3OP( "return_call", 0, any, d_emptyOpList, Compile_Call ), // 0x12 TODO: Optimize
M3OP( "return_call_indirect",0, any, d_emptyOpList, Compile_CallIndirect ), // 0x13
M3OP_RESERVED, M3OP_RESERVED, // 0x14 - 0x15
M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, // 0x16 - 0x19
M3OP( "drop", -1, none, d_emptyOpList, Compile_Drop ), // 0x1a
M3OP( "select", -2, any, d_emptyOpList, Compile_Select ), // 0x1b
M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, // 0x1c - 0x1f
M3OP( "local.get", 1, any, d_emptyOpList, Compile_GetLocal ), // 0x20
M3OP( "local.set", 1, none, d_emptyOpList, Compile_SetLocal ), // 0x21
M3OP( "local.tee", 0, any, d_emptyOpList, Compile_SetLocal ), // 0x22
M3OP( "global.get", 1, none, d_emptyOpList, Compile_GetSetGlobal ), // 0x23
M3OP( "global.set", 1, none, d_emptyOpList, Compile_GetSetGlobal ), // 0x24
M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, // 0x25 - 0x27
M3OP( "i32.load", 0, i_32, d_unaryOpList (i32, Load_i32), Compile_Load_Store ), // 0x28
M3OP( "i64.load", 0, i_64, d_unaryOpList (i64, Load_i64), Compile_Load_Store ), // 0x29
M3OP_F( "f32.load", 0, f_32, d_unaryOpList (f32, Load_f32), Compile_Load_Store ), // 0x2a
M3OP_F( "f64.load", 0, f_64, d_unaryOpList (f64, Load_f64), Compile_Load_Store ), // 0x2b
M3OP( "i32.load8_s", 0, i_32, d_unaryOpList (i32, Load_i8), Compile_Load_Store ), // 0x2c
M3OP( "i32.load8_u", 0, i_32, d_unaryOpList (i32, Load_u8), Compile_Load_Store ), // 0x2d
M3OP( "i32.load16_s", 0, i_32, d_unaryOpList (i32, Load_i16), Compile_Load_Store ), // 0x2e
M3OP( "i32.load16_u", 0, i_32, d_unaryOpList (i32, Load_u16), Compile_Load_Store ), // 0x2f
M3OP( "i64.load8_s", 0, i_64, d_unaryOpList (i64, Load_i8), Compile_Load_Store ), // 0x30
M3OP( "i64.load8_u", 0, i_64, d_unaryOpList (i64, Load_u8), Compile_Load_Store ), // 0x31
M3OP( "i64.load16_s", 0, i_64, d_unaryOpList (i64, Load_i16), Compile_Load_Store ), // 0x32
M3OP( "i64.load16_u", 0, i_64, d_unaryOpList (i64, Load_u16), Compile_Load_Store ), // 0x33
M3OP( "i64.load32_s", 0, i_64, d_unaryOpList (i64, Load_i32), Compile_Load_Store ), // 0x34
M3OP( "i64.load32_u", 0, i_64, d_unaryOpList (i64, Load_u32), Compile_Load_Store ), // 0x35
M3OP( "i32.store", -2, none, d_binOpList (i32, Store_i32), Compile_Load_Store ), // 0x36
M3OP( "i64.store", -2, none, d_binOpList (i64, Store_i64), Compile_Load_Store ), // 0x37
M3OP_F( "f32.store", -2, none, d_storeFpOpList (f32, Store_f32), Compile_Load_Store ), // 0x38
M3OP_F( "f64.store", -2, none, d_storeFpOpList (f64, Store_f64), Compile_Load_Store ), // 0x39
M3OP( "i32.store8", -2, none, d_binOpList (i32, Store_u8), Compile_Load_Store ), // 0x3a
M3OP( "i32.store16", -2, none, d_binOpList (i32, Store_i16), Compile_Load_Store ), // 0x3b
M3OP( "i64.store8", -2, none, d_binOpList (i64, Store_u8), Compile_Load_Store ), // 0x3c
M3OP( "i64.store16", -2, none, d_binOpList (i64, Store_i16), Compile_Load_Store ), // 0x3d
M3OP( "i64.store32", -2, none, d_binOpList (i64, Store_i32), Compile_Load_Store ), // 0x3e
M3OP( "memory.current", 1, i_32, d_logOp (MemCurrent), Compile_Memory_Current ), // 0x3f
M3OP( "memory.grow", 1, i_32, d_logOp (MemGrow), Compile_Memory_Grow ), // 0x40
M3OP( "i32.const", 1, i_32, d_logOp (Const32), Compile_Const_i32 ), // 0x41
M3OP( "i64.const", 1, i_64, d_logOp (Const64), Compile_Const_i64 ), // 0x42
M3OP_F( "f32.const", 1, f_32, d_emptyOpList, Compile_Const_f32 ), // 0x43
M3OP_F( "f64.const", 1, f_64, d_emptyOpList, Compile_Const_f64 ), // 0x44
M3OP( "i32.eqz", 0, i_32, d_unaryOpList (i32, EqualToZero) , NULL ), // 0x45
M3OP( "i32.eq", -1, i_32, d_commutativeBinOpList (i32, Equal) , NULL ), // 0x46
M3OP( "i32.ne", -1, i_32, d_commutativeBinOpList (i32, NotEqual) , NULL ), // 0x47
M3OP( "i32.lt_s", -1, i_32, d_binOpList (i32, LessThan) , NULL ), // 0x48
M3OP( "i32.lt_u", -1, i_32, d_binOpList (u32, LessThan) , NULL ), // 0x49
M3OP( "i32.gt_s", -1, i_32, d_binOpList (i32, GreaterThan) , NULL ), // 0x4a
M3OP( "i32.gt_u", -1, i_32, d_binOpList (u32, GreaterThan) , NULL ), // 0x4b
M3OP( "i32.le_s", -1, i_32, d_binOpList (i32, LessThanOrEqual) , NULL ), // 0x4c
M3OP( "i32.le_u", -1, i_32, d_binOpList (u32, LessThanOrEqual) , NULL ), // 0x4d
M3OP( "i32.ge_s", -1, i_32, d_binOpList (i32, GreaterThanOrEqual) , NULL ), // 0x4e
M3OP( "i32.ge_u", -1, i_32, d_binOpList (u32, GreaterThanOrEqual) , NULL ), // 0x4f
M3OP( "i64.eqz", 0, i_32, d_unaryOpList (i64, EqualToZero) , NULL ), // 0x50
M3OP( "i64.eq", -1, i_32, d_commutativeBinOpList (i64, Equal) , NULL ), // 0x51
M3OP( "i64.ne", -1, i_32, d_commutativeBinOpList (i64, NotEqual) , NULL ), // 0x52
M3OP( "i64.lt_s", -1, i_32, d_binOpList (i64, LessThan) , NULL ), // 0x53
M3OP( "i64.lt_u", -1, i_32, d_binOpList (u64, LessThan) , NULL ), // 0x54
M3OP( "i64.gt_s", -1, i_32, d_binOpList (i64, GreaterThan) , NULL ), // 0x55
M3OP( "i64.gt_u", -1, i_32, d_binOpList (u64, GreaterThan) , NULL ), // 0x56
M3OP( "i64.le_s", -1, i_32, d_binOpList (i64, LessThanOrEqual) , NULL ), // 0x57
M3OP( "i64.le_u", -1, i_32, d_binOpList (u64, LessThanOrEqual) , NULL ), // 0x58
M3OP( "i64.ge_s", -1, i_32, d_binOpList (i64, GreaterThanOrEqual) , NULL ), // 0x59
M3OP( "i64.ge_u", -1, i_32, d_binOpList (u64, GreaterThanOrEqual) , NULL ), // 0x5a
M3OP_F( "f32.eq", -1, i_32, d_commutativeBinOpList (f32, Equal) , NULL ), // 0x5b
M3OP_F( "f32.ne", -1, i_32, d_commutativeBinOpList (f32, NotEqual) , NULL ), // 0x5c
M3OP_F( "f32.lt", -1, i_32, d_binOpList (f32, LessThan) , NULL ), // 0x5d
M3OP_F( "f32.gt", -1, i_32, d_binOpList (f32, GreaterThan) , NULL ), // 0x5e
M3OP_F( "f32.le", -1, i_32, d_binOpList (f32, LessThanOrEqual) , NULL ), // 0x5f
M3OP_F( "f32.ge", -1, i_32, d_binOpList (f32, GreaterThanOrEqual) , NULL ), // 0x60
M3OP_F( "f64.eq", -1, i_32, d_commutativeBinOpList (f64, Equal) , NULL ), // 0x61
M3OP_F( "f64.ne", -1, i_32, d_commutativeBinOpList (f64, NotEqual) , NULL ), // 0x62
M3OP_F( "f64.lt", -1, i_32, d_binOpList (f64, LessThan) , NULL ), // 0x63
M3OP_F( "f64.gt", -1, i_32, d_binOpList (f64, GreaterThan) , NULL ), // 0x64
M3OP_F( "f64.le", -1, i_32, d_binOpList (f64, LessThanOrEqual) , NULL ), // 0x65
M3OP_F( "f64.ge", -1, i_32, d_binOpList (f64, GreaterThanOrEqual) , NULL ), // 0x66
M3OP( "i32.clz", 0, i_32, d_unaryOpList (u32, Clz) , NULL ), // 0x67
M3OP( "i32.ctz", 0, i_32, d_unaryOpList (u32, Ctz) , NULL ), // 0x68
M3OP( "i32.popcnt", 0, i_32, d_unaryOpList (u32, Popcnt) , NULL ), // 0x69
M3OP( "i32.add", -1, i_32, d_commutativeBinOpList (i32, Add) , NULL ), // 0x6a
M3OP( "i32.sub", -1, i_32, d_binOpList (i32, Subtract) , NULL ), // 0x6b
M3OP( "i32.mul", -1, i_32, d_commutativeBinOpList (i32, Multiply) , NULL ), // 0x6c
M3OP( "i32.div_s", -1, i_32, d_binOpList (i32, Divide) , NULL ), // 0x6d
M3OP( "i32.div_u", -1, i_32, d_binOpList (u32, Divide) , NULL ), // 0x6e
M3OP( "i32.rem_s", -1, i_32, d_binOpList (i32, Remainder) , NULL ), // 0x6f
M3OP( "i32.rem_u", -1, i_32, d_binOpList (u32, Remainder) , NULL ), // 0x70
M3OP( "i32.and", -1, i_32, d_commutativeBinOpList (u32, And) , NULL ), // 0x71
M3OP( "i32.or", -1, i_32, d_commutativeBinOpList (u32, Or) , NULL ), // 0x72
M3OP( "i32.xor", -1, i_32, d_commutativeBinOpList (u32, Xor) , NULL ), // 0x73
M3OP( "i32.shl", -1, i_32, d_binOpList (u32, ShiftLeft) , NULL ), // 0x74
M3OP( "i32.shr_s", -1, i_32, d_binOpList (i32, ShiftRight) , NULL ), // 0x75
M3OP( "i32.shr_u", -1, i_32, d_binOpList (u32, ShiftRight) , NULL ), // 0x76
M3OP( "i32.rotl", -1, i_32, d_binOpList (u32, Rotl) , NULL ), // 0x77
M3OP( "i32.rotr", -1, i_32, d_binOpList (u32, Rotr) , NULL ), // 0x78
M3OP( "i64.clz", 0, i_64, d_unaryOpList (u64, Clz) , NULL ), // 0x79
M3OP( "i64.ctz", 0, i_64, d_unaryOpList (u64, Ctz) , NULL ), // 0x7a
M3OP( "i64.popcnt", 0, i_64, d_unaryOpList (u64, Popcnt) , NULL ), // 0x7b
M3OP( "i64.add", -1, i_64, d_commutativeBinOpList (i64, Add) , NULL ), // 0x7c
M3OP( "i64.sub", -1, i_64, d_binOpList (i64, Subtract) , NULL ), // 0x7d
M3OP( "i64.mul", -1, i_64, d_commutativeBinOpList (i64, Multiply) , NULL ), // 0x7e
M3OP( "i64.div_s", -1, i_64, d_binOpList (i64, Divide) , NULL ), // 0x7f
M3OP( "i64.div_u", -1, i_64, d_binOpList (u64, Divide) , NULL ), // 0x80
M3OP( "i64.rem_s", -1, i_64, d_binOpList (i64, Remainder) , NULL ), // 0x81
M3OP( "i64.rem_u", -1, i_64, d_binOpList (u64, Remainder) , NULL ), // 0x82
M3OP( "i64.and", -1, i_64, d_commutativeBinOpList (u64, And) , NULL ), // 0x83
M3OP( "i64.or", -1, i_64, d_commutativeBinOpList (u64, Or) , NULL ), // 0x84
M3OP( "i64.xor", -1, i_64, d_commutativeBinOpList (u64, Xor) , NULL ), // 0x85
M3OP( "i64.shl", -1, i_64, d_binOpList (u64, ShiftLeft) , NULL ), // 0x86
M3OP( "i64.shr_s", -1, i_64, d_binOpList (i64, ShiftRight) , NULL ), // 0x87
M3OP( "i64.shr_u", -1, i_64, d_binOpList (u64, ShiftRight) , NULL ), // 0x88
M3OP( "i64.rotl", -1, i_64, d_binOpList (u64, Rotl) , NULL ), // 0x89
M3OP( "i64.rotr", -1, i_64, d_binOpList (u64, Rotr) , NULL ), // 0x8a
M3OP_F( "f32.abs", 0, f_32, d_unaryOpList(f32, Abs) , NULL ), // 0x8b
M3OP_F( "f32.neg", 0, f_32, d_unaryOpList(f32, Negate) , NULL ), // 0x8c
M3OP_F( "f32.ceil", 0, f_32, d_unaryOpList(f32, Ceil) , NULL ), // 0x8d
M3OP_F( "f32.floor", 0, f_32, d_unaryOpList(f32, Floor) , NULL ), // 0x8e
M3OP_F( "f32.trunc", 0, f_32, d_unaryOpList(f32, Trunc) , NULL ), // 0x8f
M3OP_F( "f32.nearest", 0, f_32, d_unaryOpList(f32, Nearest) , NULL ), // 0x90
M3OP_F( "f32.sqrt", 0, f_32, d_unaryOpList(f32, Sqrt) , NULL ), // 0x91
M3OP_F( "f32.add", -1, f_32, d_commutativeBinOpList (f32, Add) , NULL ), // 0x92
M3OP_F( "f32.sub", -1, f_32, d_binOpList (f32, Subtract) , NULL ), // 0x93
M3OP_F( "f32.mul", -1, f_32, d_commutativeBinOpList (f32, Multiply) , NULL ), // 0x94
M3OP_F( "f32.div", -1, f_32, d_binOpList (f32, Divide) , NULL ), // 0x95
M3OP_F( "f32.min", -1, f_32, d_commutativeBinOpList (f32, Min) , NULL ), // 0x96
M3OP_F( "f32.max", -1, f_32, d_commutativeBinOpList (f32, Max) , NULL ), // 0x97
M3OP_F( "f32.copysign", -1, f_32, d_binOpList (f32, CopySign) , NULL ), // 0x98
M3OP_F( "f64.abs", 0, f_64, d_unaryOpList(f64, Abs) , NULL ), // 0x99
M3OP_F( "f64.neg", 0, f_64, d_unaryOpList(f64, Negate) , NULL ), // 0x9a
M3OP_F( "f64.ceil", 0, f_64, d_unaryOpList(f64, Ceil) , NULL ), // 0x9b
M3OP_F( "f64.floor", 0, f_64, d_unaryOpList(f64, Floor) , NULL ), // 0x9c
M3OP_F( "f64.trunc", 0, f_64, d_unaryOpList(f64, Trunc) , NULL ), // 0x9d
M3OP_F( "f64.nearest", 0, f_64, d_unaryOpList(f64, Nearest) , NULL ), // 0x9e
M3OP_F( "f64.sqrt", 0, f_64, d_unaryOpList(f64, Sqrt) , NULL ), // 0x9f
M3OP_F( "f64.add", -1, f_64, d_commutativeBinOpList (f64, Add) , NULL ), // 0xa0
M3OP_F( "f64.sub", -1, f_64, d_binOpList (f64, Subtract) , NULL ), // 0xa1
M3OP_F( "f64.mul", -1, f_64, d_commutativeBinOpList (f64, Multiply) , NULL ), // 0xa2
M3OP_F( "f64.div", -1, f_64, d_binOpList (f64, Divide) , NULL ), // 0xa3
M3OP_F( "f64.min", -1, f_64, d_commutativeBinOpList (f64, Min) , NULL ), // 0xa4
M3OP_F( "f64.max", -1, f_64, d_commutativeBinOpList (f64, Max) , NULL ), // 0xa5
M3OP_F( "f64.copysign", -1, f_64, d_binOpList (f64, CopySign) , NULL ), // 0xa6
M3OP( "i32.wrap/i64", 0, i_32, d_unaryOpList (i32, Wrap_i64), NULL ), // 0xa7
M3OP_F( "i32.trunc_s/f32", 0, i_32, d_convertOpList (i32_Trunc_f32), Compile_Convert ), // 0xa8
M3OP_F( "i32.trunc_u/f32", 0, i_32, d_convertOpList (u32_Trunc_f32), Compile_Convert ), // 0xa9
M3OP_F( "i32.trunc_s/f64", 0, i_32, d_convertOpList (i32_Trunc_f64), Compile_Convert ), // 0xaa
M3OP_F( "i32.trunc_u/f64", 0, i_32, d_convertOpList (u32_Trunc_f64), Compile_Convert ), // 0xab
M3OP( "i64.extend_s/i32", 0, i_64, d_unaryOpList (i64, Extend_i32), NULL ), // 0xac
M3OP( "i64.extend_u/i32", 0, i_64, d_unaryOpList (i64, Extend_u32), NULL ), // 0xad
M3OP_F( "i64.trunc_s/f32", 0, i_64, d_convertOpList (i64_Trunc_f32), Compile_Convert ), // 0xae
M3OP_F( "i64.trunc_u/f32", 0, i_64, d_convertOpList (u64_Trunc_f32), Compile_Convert ), // 0xaf
M3OP_F( "i64.trunc_s/f64", 0, i_64, d_convertOpList (i64_Trunc_f64), Compile_Convert ), // 0xb0
M3OP_F( "i64.trunc_u/f64", 0, i_64, d_convertOpList (u64_Trunc_f64), Compile_Convert ), // 0xb1
M3OP_F( "f32.convert_s/i32",0, f_32, d_convertOpList (f32_Convert_i32), Compile_Convert ), // 0xb2
M3OP_F( "f32.convert_u/i32",0, f_32, d_convertOpList (f32_Convert_u32), Compile_Convert ), // 0xb3
M3OP_F( "f32.convert_s/i64",0, f_32, d_convertOpList (f32_Convert_i64), Compile_Convert ), // 0xb4
M3OP_F( "f32.convert_u/i64",0, f_32, d_convertOpList (f32_Convert_u64), Compile_Convert ), // 0xb5
M3OP_F( "f32.demote/f64", 0, f_32, d_unaryOpList (f32, Demote_f64), NULL ), // 0xb6
M3OP_F( "f64.convert_s/i32",0, f_64, d_convertOpList (f64_Convert_i32), Compile_Convert ), // 0xb7
M3OP_F( "f64.convert_u/i32",0, f_64, d_convertOpList (f64_Convert_u32), Compile_Convert ), // 0xb8
M3OP_F( "f64.convert_s/i64",0, f_64, d_convertOpList (f64_Convert_i64), Compile_Convert ), // 0xb9
M3OP_F( "f64.convert_u/i64",0, f_64, d_convertOpList (f64_Convert_u64), Compile_Convert ), // 0xba
M3OP_F( "f64.promote/f32", 0, f_64, d_unaryOpList (f64, Promote_f32), NULL ), // 0xbb
M3OP_F( "i32.reinterpret/f32",0,i_32, d_convertOpList (i32_Reinterpret_f32), Compile_Convert ), // 0xbc
M3OP_F( "i64.reinterpret/f64",0,i_64, d_convertOpList (i64_Reinterpret_f64), Compile_Convert ), // 0xbd
M3OP_F( "f32.reinterpret/i32",0,f_32, d_convertOpList (f32_Reinterpret_i32), Compile_Convert ), // 0xbe
M3OP_F( "f64.reinterpret/i64",0,f_64, d_convertOpList (f64_Reinterpret_i64), Compile_Convert ), // 0xbf
M3OP( "i32.extend8_s", 0, i_32, d_unaryOpList (i32, Extend8_s), NULL ), // 0xc0
M3OP( "i32.extend16_s", 0, i_32, d_unaryOpList (i32, Extend16_s), NULL ), // 0xc1
M3OP( "i64.extend8_s", 0, i_64, d_unaryOpList (i64, Extend8_s), NULL ), // 0xc2
M3OP( "i64.extend16_s", 0, i_64, d_unaryOpList (i64, Extend16_s), NULL ), // 0xc3
M3OP( "i64.extend32_s", 0, i_64, d_unaryOpList (i64, Extend32_s), NULL ), // 0xc4
# ifdef DEBUG // for codepage logging:
# define d_m3DebugOp(OP) M3OP (#OP, 0, none, { op_##OP })
# define d_m3DebugTypedOp(OP) M3OP (#OP, 0, none, { op_##OP##_i32, op_##OP##_i64, op_##OP##_f32, op_##OP##_f64, })
d_m3DebugOp (Entry), d_m3DebugOp (Compile), d_m3DebugOp (End),
d_m3DebugOp (Unsupported),
d_m3DebugOp (CallRawFunction),
d_m3DebugOp (GetGlobal_s32), d_m3DebugOp (GetGlobal_s64), d_m3DebugOp (ContinueLoop), d_m3DebugOp (ContinueLoopIf),
d_m3DebugOp (CopySlot_32), d_m3DebugOp (PreserveCopySlot_32),
d_m3DebugOp (CopySlot_64), d_m3DebugOp (PreserveCopySlot_64),
d_m3DebugOp (i32_BranchIf_rs), d_m3DebugOp (i32_BranchIf_ss), d_m3DebugOp (i64_BranchIf_rs), d_m3DebugOp (i64_BranchIf_ss),
d_m3DebugOp (Select_i32_rss), d_m3DebugOp (Select_i32_srs), d_m3DebugOp (Select_i32_ssr), d_m3DebugOp (Select_i32_sss),
d_m3DebugOp (Select_i64_rss), d_m3DebugOp (Select_i64_srs), d_m3DebugOp (Select_i64_ssr), d_m3DebugOp (Select_i64_sss),
d_m3DebugOp (Select_f32_sss), d_m3DebugOp (Select_f32_srs), d_m3DebugOp (Select_f32_ssr),
d_m3DebugOp (Select_f32_rss), d_m3DebugOp (Select_f32_rrs), d_m3DebugOp (Select_f32_rsr),
d_m3DebugOp (Select_f64_sss), d_m3DebugOp (Select_f64_srs), d_m3DebugOp (Select_f64_ssr),
d_m3DebugOp (Select_f64_rss), d_m3DebugOp (Select_f64_rrs), d_m3DebugOp (Select_f64_rsr),
d_m3DebugTypedOp (SetGlobal), d_m3DebugOp (SetGlobal_s32), d_m3DebugOp (SetGlobal_s64),
d_m3DebugTypedOp (SetRegister), d_m3DebugTypedOp (SetSlot), d_m3DebugTypedOp (PreserveSetSlot),
# endif
# ifdef d_m3CompileExtendedOpcode
[0xFC] = M3OP( "0xFC", 0, c_m3Type_unknown, d_emptyOpList, Compile_ExtendedOpcode ),
# endif
# ifdef DEBUG
M3OP( "termination", 0, c_m3Type_unknown ) // for find_operation_info
# endif
};
const M3OpInfo c_operationsFC [] =
{
M3OP_F( "i32.trunc_s:sat/f32",0, i_32, d_convertOpList (i32_TruncSat_f32), Compile_Convert ), // 0x00
M3OP_F( "i32.trunc_u:sat/f32",0, i_32, d_convertOpList (u32_TruncSat_f32), Compile_Convert ), // 0x01
M3OP_F( "i32.trunc_s:sat/f64",0, i_32, d_convertOpList (i32_TruncSat_f64), Compile_Convert ), // 0x02
M3OP_F( "i32.trunc_u:sat/f64",0, i_32, d_convertOpList (u32_TruncSat_f64), Compile_Convert ), // 0x03
M3OP_F( "i64.trunc_s:sat/f32",0, i_64, d_convertOpList (i64_TruncSat_f32), Compile_Convert ), // 0x04
M3OP_F( "i64.trunc_u:sat/f32",0, i_64, d_convertOpList (u64_TruncSat_f32), Compile_Convert ), // 0x05
M3OP_F( "i64.trunc_s:sat/f64",0, i_64, d_convertOpList (i64_TruncSat_f64), Compile_Convert ), // 0x06
M3OP_F( "i64.trunc_u:sat/f64",0, i_64, d_convertOpList (u64_TruncSat_f64), Compile_Convert ), // 0x07
# ifdef DEBUG
M3OP( "termination", 0, c_m3Type_unknown ) // for find_operation_info
# endif
};
M3Result Compile_BlockStatements (IM3Compilation o)
{
M3Result result = m3Err_none;
while (o->wasm < o->wasmEnd)
{ emit_stack_dump (o);
o->lastOpcodeStart = o->wasm;
m3opcode_t opcode = * (o->wasm++); log_opcode (o, opcode);
#ifndef d_m3CompileExtendedOpcode
if (UNLIKELY(opcode == 0xFC)) {
opcode = (opcode << 8) | (* (o->wasm++));
}
#endif
IM3OpInfo opinfo = GetOpInfo(opcode);
_throwif(m3Err_unknownOpcode, opinfo == NULL);
if (opinfo->compiler) {
result = (* opinfo->compiler) (o, opcode);
} else {
result = Compile_Operator(o, opcode);
}
o->previousOpcode = opcode; // m3logif (stack, dump_type_stack (o))
if (o->stackIndex > d_m3MaxFunctionStackHeight) // TODO: is this only place to check?
result = m3Err_functionStackOverflow;
if (result)
break;
if (opcode == c_waOp_end or opcode == c_waOp_else)
break;
}
_catch:
return result;
}
M3Result ValidateBlockEnd (IM3Compilation o, bool * o_copyStackTopToRegister)
{
M3Result result = m3Err_none;
* o_copyStackTopToRegister = false;
u8 valueType = GetSingleRetType(o->block.type);
if (valueType != c_m3Type_none)
{
if (IsStackPolymorphic (o))
{
_ (UnwindBlockStack (o));
_ (PushRegister (o, valueType));
}
else
{
i16 initStackIndex = o->block.initStackIndex;
if (o->block.depth > 0 and initStackIndex != o->stackIndex)
{
if (o->stackIndex == initStackIndex + 1)
{
* o_copyStackTopToRegister = IsStackTopInSlot (o);
}
else _throw ("unexpected block stack offset");
}
}
}
else
_ (UnwindBlockStack (o));
_catch: return result;
}
M3Result CompileBlock (IM3Compilation o, /*pc_t * o_startPC,*/ IM3FuncType i_blockType, u8 i_blockOpcode)
{
M3Result result; d_m3Assert (not IsRegisterAllocated (o, 0));
d_m3Assert (not IsRegisterAllocated (o, 1));
M3CompilationScope outerScope = o->block;
M3CompilationScope * block = & o->block;
block->outer = & outerScope;
block->pc = GetPagePC (o->page);
block->patches = NULL;
block->type = i_blockType;
block->initStackIndex = o->stackIndex;
block->depth ++;
// block->loopDepth += (i_blockOpcode == c_waOp_loop);
block->opcode = i_blockOpcode;
_ (Compile_BlockStatements (o));
bool moveStackTopToRegister;
_ (ValidateBlockEnd (o, & moveStackTopToRegister));
if (moveStackTopToRegister)
_ (MoveStackTopToRegister (o));
PatchBranches (o);
o->block = outerScope;
_catch: return result;
}
M3Result CompileLocals (IM3Compilation o)
{
M3Result result;
u32 numLocalBlocks;
_ (ReadLEB_u32 (& numLocalBlocks, & o->wasm, o->wasmEnd));
for (u32 l = 0; l < numLocalBlocks; ++l)
{
u32 varCount;
i8 waType;
u8 localType;
_ (ReadLEB_u32 (& varCount, & o->wasm, o->wasmEnd));
_ (ReadLEB_i7 (& waType, & o->wasm, o->wasmEnd));
_ (NormalizeType (& localType, waType));
m3log (compile, "pushing locals. count: %d; type: %s", varCount, c_waTypes [localType]);
while (varCount--)
_ (PushAllocatedSlot (o, localType));
}
_catch: return result;
}
M3Result Compile_ReserveConstants (IM3Compilation o)
{
M3Result result = m3Err_none;
// in the interest of speed, this blindly scans the Wasm code looking for any byte
// that looks like an const opcode.
u32 numConstantSlots = 0;
bytes_t wa = o->wasm;
while (wa < o->wasmEnd)
{
u8 code = * wa++;
if (code == 0x41 or code == 0x43) // i32, f32
numConstantSlots += 1;
else if (code == 0x42 or code == 0x44) // i64, f64
numConstantSlots += GetTypeNumSlots (c_m3Type_i64);
} m3log (compile, "estimated constant slots: %d", numConstantSlots)
// if constants overflow their reserved stack space, the compiler simply emits op_Const
// operations as needed. Compiled expressions (global inits) don't pass through this
// ReserveConstants function and thus always produce inline contants.
numConstantSlots = M3_MIN (numConstantSlots, d_m3MaxConstantTableSize);
o->firstDynamicSlotIndex = o->firstConstSlotIndex + numConstantSlots;
if (o->firstDynamicSlotIndex >= d_m3MaxFunctionSlots)
result = m3Err_functionStackOverflow;
return result;
}
void SetupCompilation (IM3Compilation o)
{
IM3BranchPatch patches = o->releasedPatches;
memset (o, 0x0, sizeof (M3Compilation));
o->releasedPatches = patches;
}
M3Result Compile_Function (IM3Function io_function)
{
IM3FuncType ft = io_function->funcType;
M3Result result = m3Err_none; m3log (compile, "compiling: '%s'; wasm-size: %d; numArgs: %d; return: %s",
m3_GetFunctionName(io_function), (u32) (io_function->wasmEnd - io_function->wasm), GetFunctionNumArgs (io_function), c_waTypes [GetSingleRetType(ft)]);
IM3Runtime runtime = io_function->module->runtime;
IM3Compilation o = & runtime->compilation;
SetupCompilation (o);
o->runtime = runtime;
o->module = io_function->module;
o->function = io_function;
o->wasm = io_function->wasm;
o->wasmEnd = io_function->wasmEnd;
_try {
_ (AcquireCompilationCodePage (o, & o->page));
pc_t pc = GetPagePC (o->page);
// push the arg types to the type stack
o->block.type = ft;
// all args are 64-bit aligned
const u32 argSlotCount = sizeof (u64) / sizeof (m3slot_t);
u32 numArgs = GetFunctionNumArgs (o->function);
u32 numRets = GetFunctionNumReturns(o->function);
for (u32 i = 0; i < numArgs; ++i)
{
u8 type = ft->types [numRets + i];
_ (PushAllocatedSlot (o, type));
if (i < numArgs - 1)
{
// prevent allocator fill-in
o->firstDynamicSlotIndex += argSlotCount;
}
else
{
// final arg only allocates its natural width when using 32-bit slots
o->firstDynamicSlotIndex += GetTypeNumSlots (type);
}
}
o->function->numArgSlots = o->firstLocalSlotIndex = o->firstDynamicSlotIndex;
_ (CompileLocals (o));
u16 maxSlot = GetMaxUsedSlotPlusOne (o);
o->function->numLocalBytes = (maxSlot - o->firstLocalSlotIndex) * sizeof (m3slot_t);
o->firstConstSlotIndex = o->maxConstSlotIndex = maxSlot;
// ReserveConstants initializes o->firstDynamicSlotIndex
_ (Compile_ReserveConstants (o));
// start tracking the max stack used (Push() also updates this value) so that op_Entry can precisely detect stack overflow
o->function->maxStackSlots = o->maxAllocatedSlotPlusOne = o->firstDynamicSlotIndex;
o->block.initStackIndex = o->firstDynamicStackIndex = o->stackIndex; m3log (compile, "start stack index: %d", (u32) o->firstDynamicStackIndex);
_ (EmitOp (o, op_Entry));
EmitPointer (o, io_function);
_ (Compile_BlockStatements (o));
io_function->compiled = pc;
u32 numConstantSlots = o->maxConstSlotIndex - o->firstConstSlotIndex; m3log (compile, "unique constant slots: %d; unused slots: %d", numConstantSlots, o->firstDynamicSlotIndex - o->maxConstSlotIndex);
io_function->numConstantBytes = numConstantSlots * sizeof (m3slot_t);
if (numConstantSlots)
{
_ (m3CopyMem (& io_function->constants, o->constants, io_function->numConstantBytes));
}
} _catch:
ReleaseCompilationCodePage (o);
return result;
}