blob: 1f03b37fdef43c6f48988495fa9ea7d2c3f13f03 [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft Corporation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "WasmReaderPch.h"
#ifdef ENABLE_WASM
#include "Language/WebAssemblySource.h"
#include "ByteCode/WasmByteCodeWriter.h"
#include "EmptyWasmByteCodeWriter.h"
#if DBG_DUMP
uint32 opId = 0;
uint32 lastOpId = 1;
#define DebugPrintOp(op) if (PHASE_TRACE(Js::WasmBytecodePhase, GetFunctionBody())) { PrintOpBegin(op); }
#define DebugPrintOpEnd() if (PHASE_TRACE(Js::WasmBytecodePhase, GetFunctionBody())) { PrintOpEnd(); }
#else
#define DebugPrintOp(op)
#define DebugPrintOpEnd()
#endif
namespace Wasm
{
#define WASM_SIGNATURE(id, nTypes, ...) const WasmTypes::WasmType WasmOpCodeSignatures::id[] = {__VA_ARGS__};
#include "WasmBinaryOpCodes.h"
#if DBG_DUMP
void PrintTypeStack(const JsUtil::Stack<EmitInfo>& stack)
{
Output::Print(_u("["));
int i = 0;
while (stack.Peek(i).type != WasmTypes::Limit)
{
++i;
}
--i;
bool isFirst = true;
while (i >= 0)
{
EmitInfo info = stack.Peek(i--);
if (!isFirst)
{
Output::Print(_u(", "));
}
isFirst = false;
switch (info.type)
{
case WasmTypes::I32: Output::Print(_u("i32")); break;
case WasmTypes::I64: Output::Print(_u("i64")); break;
case WasmTypes::F32: Output::Print(_u("f32")); break;
case WasmTypes::F64: Output::Print(_u("f64")); break;
default: Output::Print(_u("any")); break;
}
}
Output::Print(_u("]"));
}
void WasmBytecodeGenerator::PrintOpBegin(WasmOp op) const
{
if (lastOpId == opId) Output::Print(_u("\r\n"));
lastOpId = ++opId;
const int depth = m_blockInfos.Count() - 1;
if (depth > 0)
{
Output::SkipToColumn(depth);
}
switch (op)
{
#define WASM_OPCODE(opname, opcode, sig, nyi) \
case wb##opname: \
Output::Print(_u(#opname)); \
break;
#include "WasmBinaryOpCodes.h"
}
Output::SkipToColumn(20);
PrintTypeStack(m_evalStack);
}
void WasmBytecodeGenerator::PrintOpEnd() const
{
if (lastOpId == opId)
{
++opId;
Output::Print(_u(" -> "));
PrintTypeStack(m_evalStack);
Output::Print(_u("\r\n"));
}
}
#endif
/* static */
Js::AsmJsRetType WasmToAsmJs::GetAsmJsReturnType(WasmTypes::WasmType wasmType)
{
switch (wasmType)
{
case WasmTypes::I32: return Js::AsmJsRetType::Signed;
case WasmTypes::I64: return Js::AsmJsRetType::Int64;
case WasmTypes::F32: return Js::AsmJsRetType::Float;
case WasmTypes::F64: return Js::AsmJsRetType::Double;
case WasmTypes::Void: return Js::AsmJsRetType::Void;
default:
throw WasmCompilationException(_u("Unknown return type %u"), wasmType);
}
}
/* static */
Js::AsmJsVarType WasmToAsmJs::GetAsmJsVarType(WasmTypes::WasmType wasmType)
{
Js::AsmJsVarType asmType = Js::AsmJsVarType::Int;
switch (wasmType)
{
case WasmTypes::I32: return Js::AsmJsVarType::Int;
case WasmTypes::I64: return Js::AsmJsVarType::Int64;
case WasmTypes::F32: return Js::AsmJsVarType::Float;
case WasmTypes::F64: return Js::AsmJsVarType::Double;
default:
throw WasmCompilationException(_u("Unknown var type %u"), wasmType);
}
}
typedef bool(*SectionProcessFunc)(WasmModuleGenerator*);
typedef void(*AfterSectionCallback)(WasmModuleGenerator*);
WasmModuleGenerator::WasmModuleGenerator(Js::ScriptContext* scriptContext, Js::WebAssemblySource* src) :
m_sourceInfo(src->GetSourceInfo()),
m_scriptContext(scriptContext),
m_recycler(scriptContext->GetRecycler())
{
m_module = RecyclerNewFinalized(m_recycler, Js::WebAssemblyModule, scriptContext, src->GetBuffer(), src->GetBufferLength(), scriptContext->GetLibrary()->GetWebAssemblyModuleType());
m_sourceInfo->EnsureInitialized(0);
m_sourceInfo->GetSrcInfo()->sourceContextInfo->EnsureInitialized();
}
Js::WebAssemblyModule* WasmModuleGenerator::GenerateModule()
{
m_module->GetReader()->InitializeReader();
BVStatic<bSectLimit + 1> visitedSections;
SectionCode nextExpectedSection = bSectCustom;
while (true)
{
SectionHeader sectionHeader = GetReader()->ReadNextSection();
SectionCode sectionCode = sectionHeader.code;
if (sectionCode == bSectLimit)
{
TRACE_WASM_SECTION(_u("Done reading module's sections"));
break;
}
// Make sure dependency for this section has been seen
SectionCode precedent = SectionInfo::All[sectionCode].precedent;
if (precedent != bSectLimit && !visitedSections.Test(precedent))
{
throw WasmCompilationException(_u("%s section missing before %s"),
SectionInfo::All[precedent].name,
sectionHeader.name);
}
visitedSections.Set(sectionCode);
// Custom section are allowed in any order
if (sectionCode != bSectCustom)
{
if (sectionCode < nextExpectedSection)
{
throw WasmCompilationException(_u("Invalid Section %s"), sectionHeader.name);
}
nextExpectedSection = SectionCode(sectionCode + 1);
}
if (!GetReader()->ProcessCurrentSection())
{
throw WasmCompilationException(_u("Error while reading section %s"), sectionHeader.name);
}
}
uint32 funcCount = m_module->GetWasmFunctionCount();
for (uint32 i = 0; i < funcCount; ++i)
{
GenerateFunctionHeader(i);
}
#if ENABLE_DEBUG_CONFIG_OPTIONS
WasmFunctionInfo* firstThunk = nullptr, *lastThunk = nullptr;
for (uint32 i = 0; i < funcCount; ++i)
{
WasmFunctionInfo* info = m_module->GetWasmFunctionInfo(i);
Assert(info->GetBody());
if (PHASE_TRACE(Js::WasmInOutPhase, info->GetBody()))
{
uint32 index = m_module->GetWasmFunctionCount();
WasmFunctionInfo* newInfo = m_module->AddWasmFunctionInfo(info->GetSignature());
if (!firstThunk)
{
firstThunk = newInfo;
}
lastThunk = newInfo;
GenerateFunctionHeader(index);
m_module->SwapWasmFunctionInfo(i, index);
m_module->AttachCustomInOutTracingReader(newInfo, index);
}
}
if (firstThunk)
{
int sourceId = (int)firstThunk->GetBody()->GetSourceContextId();
char16 range[64];
swprintf_s(range, 64, _u("%d.%d-%d.%d"),
sourceId, firstThunk->GetBody()->GetLocalFunctionId(),
sourceId, lastThunk->GetBody()->GetLocalFunctionId());
char16 offFullJit[128];
swprintf_s(offFullJit, 128, _u("-off:fulljit:%s"), range);
char16 offSimpleJit[128];
swprintf_s(offSimpleJit, 128, _u("-off:simplejit:%s"), range);
char16 offLoopJit[128];
swprintf_s(offLoopJit, 128, _u("-off:jitloopbody:%s"), range);
char16* argv[] = { nullptr, offFullJit, offSimpleJit, offLoopJit };
CmdLineArgsParser parser(nullptr);
parser.Parse(ARRAYSIZE(argv), argv);
}
#endif
#if DBG_DUMP
if (PHASE_TRACE1(Js::WasmReaderPhase))
{
GetReader()->PrintOps();
}
#endif
// If we see a FunctionSignatures section we need to see a FunctionBodies section
if (visitedSections.Test(bSectFunction) && !visitedSections.Test(bSectFunctionBodies))
{
throw WasmCompilationException(_u("Missing required section: %s"), SectionInfo::All[bSectFunctionBodies].name);
}
return m_module;
}
WasmBinaryReader* WasmModuleGenerator::GetReader() const
{
return m_module->GetReader();
}
void WasmModuleGenerator::GenerateFunctionHeader(uint32 index)
{
WasmFunctionInfo* wasmInfo = m_module->GetWasmFunctionInfo(index);
if (!wasmInfo)
{
throw WasmCompilationException(_u("Invalid function index %u"), index);
}
const char16* functionName = nullptr;
int nameLength = 0;
if (wasmInfo->GetNameLength() > 0)
{
functionName = wasmInfo->GetName();
nameLength = wasmInfo->GetNameLength();
}
else
{
for (uint32 iExport = 0; iExport < m_module->GetExportCount(); ++iExport)
{
Wasm::WasmExport* wasmExport = m_module->GetExport(iExport);
if (wasmExport &&
wasmExport->kind == ExternalKinds::Function &&
wasmExport->nameLength > 0 &&
m_module->GetFunctionIndexType(wasmExport->index) == FunctionIndexTypes::Function &&
wasmExport->index == wasmInfo->GetNumber())
{
nameLength = wasmExport->nameLength + 16;
char16 * autoName = RecyclerNewArrayLeafZ(m_recycler, char16, nameLength);
nameLength = swprintf_s(autoName, nameLength, _u("%s[%u]"), wasmExport->name, wasmInfo->GetNumber());
functionName = autoName;
break;
}
}
}
if (!functionName)
{
char16* autoName = RecyclerNewArrayLeafZ(m_recycler, char16, 32);
nameLength = swprintf_s(autoName, 32, _u("wasm-function[%u]"), wasmInfo->GetNumber());
functionName = autoName;
}
Js::FunctionBody* body = Js::FunctionBody::NewFromRecycler(
m_scriptContext,
functionName,
nameLength,
0,
0,
m_sourceInfo,
m_sourceInfo->GetSrcInfo()->sourceContextInfo->sourceContextId,
wasmInfo->GetNumber(),
nullptr,
Js::FunctionInfo::Attributes::ErrorOnNew,
Js::FunctionBody::Flags_None
#ifdef PERF_COUNTERS
, false /* is function from deferred deserialized proxy */
#endif
);
wasmInfo->SetBody(body);
// TODO (michhol): numbering
body->SetSourceInfo(0);
body->AllocateAsmJsFunctionInfo();
body->SetIsAsmJsFunction(true);
body->SetIsAsmjsMode(true);
body->SetIsWasmFunction(true);
WasmReaderInfo* readerInfo = RecyclerNew(m_recycler, WasmReaderInfo);
readerInfo->m_funcInfo = wasmInfo;
readerInfo->m_module = m_module;
Js::AsmJsFunctionInfo* info = body->GetAsmJsFunctionInfo();
info->SetWasmReaderInfo(readerInfo);
info->SetWebAssemblyModule(m_module);
Js::ArgSlot paramCount = wasmInfo->GetParamCount();
info->SetArgCount(paramCount);
info->SetWasmSignature(wasmInfo->GetSignature());
Js::ArgSlot argSizeLength = max(paramCount, 3ui16);
info->SetArgSizeArrayLength(argSizeLength);
uint32* argSizeArray = RecyclerNewArrayLeafZ(m_recycler, uint32, argSizeLength);
info->SetArgsSizesArray(argSizeArray);
if (paramCount > 0)
{
// +1 here because asm.js includes the this pointer
body->SetInParamsCount(paramCount + 1);
body->SetReportedInParamsCount(paramCount + 1);
info->SetArgTypeArray(RecyclerNewArrayLeaf(m_recycler, Js::AsmJsVarType::Which, paramCount));
}
else
{
// overwrite default value in this case
body->SetHasImplicitArgIns(false);
}
for (Js::ArgSlot i = 0; i < paramCount; ++i)
{
WasmTypes::WasmType type = wasmInfo->GetSignature()->GetParam(i);
info->SetArgType(WasmToAsmJs::GetAsmJsVarType(type), i);
argSizeArray[i] = wasmInfo->GetSignature()->GetParamSize(i);
}
info->SetArgByteSize(wasmInfo->GetSignature()->GetParamsSize());
info->SetReturnType(WasmToAsmJs::GetAsmJsReturnType(wasmInfo->GetResultType()));
}
WAsmJs::RegisterSpace* AllocateRegisterSpace(ArenaAllocator* alloc, WAsmJs::Types)
{
return Anew(alloc, WAsmJs::RegisterSpace, 1);
}
void WasmBytecodeGenerator::GenerateFunctionBytecode(Js::ScriptContext* scriptContext, WasmReaderInfo* readerinfo, bool validateOnly /*= false*/)
{
WasmBytecodeGenerator generator(scriptContext, readerinfo, validateOnly);
generator.GenerateFunction();
if (!generator.GetReader()->IsCurrentFunctionCompleted())
{
throw WasmCompilationException(_u("Invalid function format"));
}
}
void WasmBytecodeGenerator::ValidateFunction(Js::ScriptContext* scriptContext, WasmReaderInfo* readerinfo)
{
GenerateFunctionBytecode(scriptContext, readerinfo, true);
}
WasmBytecodeGenerator::WasmBytecodeGenerator(Js::ScriptContext* scriptContext, WasmReaderInfo* readerInfo, bool validateOnly) :
m_scriptContext(scriptContext),
m_alloc(_u("WasmBytecodeGen"), scriptContext->GetThreadContext()->GetPageAllocator(), Js::Throw::OutOfMemory),
m_evalStack(&m_alloc),
mTypedRegisterAllocator(&m_alloc, AllocateRegisterSpace, 1 << WAsmJs::SIMD),
m_blockInfos(&m_alloc),
isUnreachable(false)
{
m_emptyWriter = Anew(&m_alloc, Js::EmptyWasmByteCodeWriter);
m_writer = m_originalWriter = validateOnly ? m_emptyWriter : Anew(&m_alloc, Js::WasmByteCodeWriter);
m_writer->Create();
m_funcInfo = readerInfo->m_funcInfo;
m_module = readerInfo->m_module;
// Init reader to current func offset
GetReader()->SeekToFunctionBody(m_funcInfo);
// Use binary size to estimate bytecode size
const uint32 astSize = readerInfo->m_funcInfo->m_readerInfo.size;
m_writer->InitData(&m_alloc, astSize);
}
void WasmBytecodeGenerator::GenerateFunction()
{
TRACE_WASM_BYTECODE(_u("GenerateFunction %u \n"), m_funcInfo->GetNumber());
if (PHASE_OFF(Js::WasmBytecodePhase, GetFunctionBody()))
{
throw WasmCompilationException(_u("Compilation skipped"));
}
Js::AutoProfilingPhase functionProfiler(m_scriptContext, Js::WasmBytecodePhase);
Unused(functionProfiler);
m_maxArgOutDepth = 0;
m_writer->Begin(GetFunctionBody(), &m_alloc);
try
{
Js::ByteCodeLabel exitLabel = m_writer->DefineLabel();
m_funcInfo->SetExitLabel(exitLabel);
EnregisterLocals();
EnterEvalStackScope();
// The function's yield type is the return type
GetReader()->m_currentNode.block.sig = m_funcInfo->GetResultType();
EmitInfo lastInfo = EmitBlock();
if (lastInfo.type != WasmTypes::Void || m_funcInfo->GetResultType() == WasmTypes::Void)
{
EmitReturnExpr(&lastInfo);
}
DebugPrintOpEnd();
ExitEvalStackScope();
SetUnreachableState(false);
m_writer->MarkAsmJsLabel(exitLabel);
m_writer->EmptyAsm(Js::OpCodeAsmJs::Ret);
m_writer->End();
GetReader()->FunctionEnd();
}
catch (...)
{
GetReader()->FunctionEnd();
m_originalWriter->Reset();
throw;
}
#if DBG_DUMP
if (PHASE_DUMP(Js::ByteCodePhase, GetFunctionBody()) && !IsValidating())
{
Js::AsmJsByteCodeDumper::Dump(GetFunctionBody(), &mTypedRegisterAllocator, nullptr);
}
#endif
Js::AsmJsFunctionInfo* info = GetFunctionBody()->GetAsmJsFunctionInfo();
mTypedRegisterAllocator.CommitToFunctionBody(GetFunctionBody());
mTypedRegisterAllocator.CommitToFunctionInfo(info, GetFunctionBody());
GetFunctionBody()->CheckAndSetOutParamMaxDepth(m_maxArgOutDepth);
}
void WasmBytecodeGenerator::EnregisterLocals()
{
uint32 nLocals = m_funcInfo->GetLocalCount();
m_locals = AnewArray(&m_alloc, WasmLocal, nLocals);
m_funcInfo->GetBody()->SetFirstTmpReg(nLocals);
for (uint32 i = 0; i < nLocals; ++i)
{
WasmTypes::WasmType type = m_funcInfo->GetLocal(i);
WasmRegisterSpace* regSpace = GetRegisterSpace(type);
if (regSpace == nullptr)
{
throw WasmCompilationException(_u("Unable to find local register space"));
}
m_locals[i] = WasmLocal(regSpace->AcquireRegister(), type);
// Zero only the locals not corresponding to formal parameters.
if (i >= m_funcInfo->GetParamCount()) {
switch (type)
{
case WasmTypes::F32:
m_writer->AsmFloat1Const1(Js::OpCodeAsmJs::Ld_FltConst, m_locals[i].location, 0.0f);
break;
case WasmTypes::F64:
m_writer->AsmDouble1Const1(Js::OpCodeAsmJs::Ld_DbConst, m_locals[i].location, 0.0);
break;
case WasmTypes::I32:
m_writer->AsmInt1Const1(Js::OpCodeAsmJs::Ld_IntConst, m_locals[i].location, 0);
break;
case WasmTypes::I64:
m_writer->AsmLong1Const1(Js::OpCodeAsmJs::Ld_LongConst, m_locals[i].location, 0);
break;
default:
Assume(UNREACHED);
}
}
}
}
void WasmBytecodeGenerator::EmitExpr(WasmOp op)
{
DebugPrintOp(op);
switch (op)
{
#define WASM_OPCODE(opname, opcode, sig, nyi) \
case opcode: \
if (nyi) throw WasmCompilationException(_u("Operator %s NYI"), _u(#opname)); break;
#include "WasmBinaryOpCodes.h"
default:
break;
}
EmitInfo info;
switch (op)
{
case wbGetGlobal:
info = EmitGetGlobal();
break;
case wbSetGlobal:
info = EmitSetGlobal();
break;
case wbGetLocal:
info = EmitGetLocal();
break;
case wbSetLocal:
info = EmitSetLocal(false);
break;
case wbTeeLocal:
info = EmitSetLocal(true);
break;
case wbReturn:
EmitReturnExpr();
info.type = WasmTypes::Any;
break;
case wbF32Const:
info = EmitConst(WasmTypes::F32, GetReader()->m_currentNode.cnst);
break;
case wbF64Const:
info = EmitConst(WasmTypes::F64, GetReader()->m_currentNode.cnst);
break;
case wbI32Const:
info = EmitConst(WasmTypes::I32, GetReader()->m_currentNode.cnst);
break;
case wbI64Const:
info = EmitConst(WasmTypes::I64, GetReader()->m_currentNode.cnst);
break;
case wbBlock:
info = EmitBlock();
break;
case wbLoop:
info = EmitLoop();
break;
case wbCall:
info = EmitCall<wbCall>();
break;
case wbCallIndirect:
info = EmitCall<wbCallIndirect>();
break;
case wbIf:
info = EmitIfElseExpr();
break;
case wbElse:
throw WasmCompilationException(_u("Unexpected else opcode"));
case wbEnd:
throw WasmCompilationException(_u("Unexpected end opcode"));
case wbBr:
EmitBr();
info.type = WasmTypes::Any;
break;
case wbBrIf:
info = EmitBrIf();
break;
case wbSelect:
info = EmitSelect();
break;
case wbBrTable:
EmitBrTable();
info.type = WasmTypes::Any;
break;
case wbDrop:
info = EmitDrop();
break;
case wbNop:
return;
case wbCurrentMemory:
{
SetUsesMemory(0);
Js::RegSlot tempReg = GetRegisterSpace(WasmTypes::I32)->AcquireTmpRegister();
info = EmitInfo(tempReg, WasmTypes::I32);
m_writer->AsmReg1(Js::OpCodeAsmJs::CurrentMemory_Int, tempReg);
break;
}
case wbGrowMemory:
{
info = EmitGrowMemory();
break;
}
case wbUnreachable:
m_writer->EmptyAsm(Js::OpCodeAsmJs::Unreachable_Void);
SetUnreachableState(true);
info.type = WasmTypes::Any;
break;
#define WASM_MEMREAD_OPCODE(opname, opcode, sig, nyi, viewtype) \
case wb##opname: \
Assert(WasmOpCodeSignatures::n##sig > 0);\
info = EmitMemAccess(wb##opname, WasmOpCodeSignatures::sig, viewtype, false); \
break;
#define WASM_MEMSTORE_OPCODE(opname, opcode, sig, nyi, viewtype) \
case wb##opname: \
Assert(WasmOpCodeSignatures::n##sig > 0);\
info = EmitMemAccess(wb##opname, WasmOpCodeSignatures::sig, viewtype, true); \
break;
#define WASM_BINARY_OPCODE(opname, opcode, sig, asmjsop, nyi) \
case wb##opname: \
Assert(WasmOpCodeSignatures::n##sig == 3);\
info = EmitBinExpr(Js::OpCodeAsmJs::##asmjsop, WasmOpCodeSignatures::sig); \
break;
#define WASM_UNARY__OPCODE(opname, opcode, sig, asmjsop, nyi) \
case wb##opname: \
Assert(WasmOpCodeSignatures::n##sig == 2);\
info = EmitUnaryExpr(Js::OpCodeAsmJs::##asmjsop, WasmOpCodeSignatures::sig); \
break;
#define WASM_EMPTY__OPCODE(opname, opcode, asmjsop, nyi) \
case wb##opname: \
m_writer->EmptyAsm(Js::OpCodeAsmJs::##asmjsop);\
break;
#include "WasmBinaryOpCodes.h"
default:
throw WasmCompilationException(_u("Unknown expression's op 0x%X"), op);
}
if (info.type != WasmTypes::Void)
{
PushEvalStack(info);
}
DebugPrintOpEnd();
}
EmitInfo WasmBytecodeGenerator::EmitGetGlobal()
{
uint32 globalIndex = GetReader()->m_currentNode.var.num;
WasmGlobal* global = m_module->GetGlobal(globalIndex);
WasmTypes::WasmType type = global->GetType();
Js::RegSlot slot = m_module->GetOffsetForGlobal(global);
CompileAssert(WasmTypes::I32 == 1);
CompileAssert(WasmTypes::I64 == 2);
CompileAssert(WasmTypes::F32 == 3);
CompileAssert(WasmTypes::F64 == 4);
static const Js::OpCodeAsmJs globalOpcodes[] = {
Js::OpCodeAsmJs::LdSlot_Int,
Js::OpCodeAsmJs::LdSlot_Long,
Js::OpCodeAsmJs::LdSlot_Flt,
Js::OpCodeAsmJs::LdSlot_Db
};
WasmRegisterSpace* regSpace = GetRegisterSpace(type);
Js::RegSlot tmpReg = regSpace->AcquireTmpRegister();
EmitInfo info(tmpReg, type);
m_writer->AsmSlot(globalOpcodes[type - 1], tmpReg, WasmBytecodeGenerator::ModuleEnvRegister, slot);
return info;
}
EmitInfo WasmBytecodeGenerator::EmitSetGlobal()
{
uint32 globalIndex = GetReader()->m_currentNode.var.num;
WasmGlobal* global = m_module->GetGlobal(globalIndex);
Js::RegSlot slot = m_module->GetOffsetForGlobal(global);
WasmTypes::WasmType type = global->GetType();
EmitInfo info = PopEvalStack(type);
CompileAssert(WasmTypes::I32 == 1);
CompileAssert(WasmTypes::I64 == 2);
CompileAssert(WasmTypes::F32 == 3);
CompileAssert(WasmTypes::F64 == 4);
static const Js::OpCodeAsmJs globalOpcodes[] = {
Js::OpCodeAsmJs::StSlot_Int,
Js::OpCodeAsmJs::StSlot_Long,
Js::OpCodeAsmJs::StSlot_Flt,
Js::OpCodeAsmJs::StSlot_Db
};
m_writer->AsmSlot(globalOpcodes[type - 1], info.location, WasmBytecodeGenerator::ModuleEnvRegister, slot);
ReleaseLocation(&info);
return EmitInfo();
}
EmitInfo WasmBytecodeGenerator::EmitGetLocal()
{
uint32 localIndex = GetReader()->m_currentNode.var.num;
if (m_funcInfo->GetLocalCount() <= localIndex)
{
throw WasmCompilationException(_u("%u is not a valid local"), localIndex);
}
WasmLocal local = m_locals[localIndex];
Js::OpCodeAsmJs op = GetLoadOp(local.type);
WasmRegisterSpace* regSpace = GetRegisterSpace(local.type);
Js::RegSlot tmpReg = regSpace->AcquireTmpRegister();
m_writer->AsmReg2(op, tmpReg, local.location);
return EmitInfo(tmpReg, local.type);
}
EmitInfo WasmBytecodeGenerator::EmitSetLocal(bool tee)
{
uint32 localNum = GetReader()->m_currentNode.var.num;
if (localNum >= m_funcInfo->GetLocalCount())
{
throw WasmCompilationException(_u("%u is not a valid local"), localNum);
}
WasmLocal local = m_locals[localNum];
EmitInfo info = PopEvalStack(local.type);
m_writer->AsmReg2(GetLoadOp(local.type), local.location, info.location);
if (tee)
{
return info;
}
else
{
ReleaseLocation(&info);
return EmitInfo();
}
}
EmitInfo WasmBytecodeGenerator::EmitConst(WasmTypes::WasmType type, WasmConstLitNode cnst)
{
Js::RegSlot tmpReg = GetRegisterSpace(type)->AcquireTmpRegister();
EmitInfo dst(tmpReg, type);
EmitLoadConst(dst, cnst);
return dst;
}
void WasmBytecodeGenerator::EmitLoadConst(EmitInfo dst, WasmConstLitNode cnst)
{
switch (dst.type)
{
case WasmTypes::F32:
m_writer->AsmFloat1Const1(Js::OpCodeAsmJs::Ld_FltConst, dst.location, cnst.f32);
break;
case WasmTypes::F64:
m_writer->AsmDouble1Const1(Js::OpCodeAsmJs::Ld_DbConst, dst.location, cnst.f64);
break;
case WasmTypes::I32:
m_writer->AsmInt1Const1(Js::OpCodeAsmJs::Ld_IntConst, dst.location, cnst.i32);
break;
case WasmTypes::I64:
m_writer->AsmLong1Const1(Js::OpCodeAsmJs::Ld_LongConst, dst.location, cnst.i64);
break;
default:
throw WasmCompilationException(_u("Unknown type %u"), dst.type);
}
}
WasmConstLitNode WasmBytecodeGenerator::GetZeroCnst()
{
WasmConstLitNode cnst = {0};
return cnst;
}
void WasmBytecodeGenerator::EnsureStackAvailable()
{
if (!ThreadContext::IsCurrentStackAvailable(Js::Constants::MinStackCompile))
{
throw WasmCompilationException(_u("Maximum supported nested blocks reached"));
}
}
void WasmBytecodeGenerator::EmitBlockCommon(BlockInfo* blockInfo, bool* endOnElse /*= nullptr*/)
{
EnsureStackAvailable();
bool canResetUnreachable = !IsUnreachable();
WasmOp op;
EnterEvalStackScope();
if(endOnElse) *endOnElse = false;
do {
op = GetReader()->ReadExpr();
if (op == wbEnd)
{
break;
}
if (endOnElse && op == wbElse)
{
*endOnElse = true;
break;
}
EmitExpr(op);
} while (true);
DebugPrintOp(op);
if (blockInfo && blockInfo->HasYield())
{
EmitInfo info = PopEvalStack();
YieldToBlock(*blockInfo, info);
ReleaseLocation(&info);
}
ExitEvalStackScope();
if (canResetUnreachable)
{
SetUnreachableState(false);
}
}
EmitInfo WasmBytecodeGenerator::EmitBlock()
{
Js::ByteCodeLabel blockLabel = m_writer->DefineLabel();
BlockInfo blockInfo = PushLabel(blockLabel);
EmitBlockCommon(&blockInfo);
m_writer->MarkAsmJsLabel(blockLabel);
EmitInfo yieldInfo = PopLabel(blockLabel);
// block yields last value
return yieldInfo;
}
EmitInfo WasmBytecodeGenerator::EmitLoop()
{
Js::ByteCodeLabel loopTailLabel = m_writer->DefineLabel();
Js::ByteCodeLabel loopHeadLabel = m_writer->DefineLabel();
Js::ByteCodeLabel loopLandingPadLabel = m_writer->DefineLabel();
uint32 loopId = m_writer->EnterLoop(loopHeadLabel);
// Internally we create a block for loop to exit, but semantically, they don't exist so pop it
BlockInfo implicitBlockInfo = PushLabel(loopTailLabel);
m_blockInfos.Pop();
// We don't want nested block to jump directly to the loop header
// instead, jump to the landing pad and let it jump back to the loop header
PushLabel(loopLandingPadLabel, false);
EmitBlockCommon(&implicitBlockInfo);
PopLabel(loopLandingPadLabel);
// By default we don't loop, jump over the landing pad
m_writer->AsmBr(loopTailLabel);
m_writer->MarkAsmJsLabel(loopLandingPadLabel);
m_writer->AsmBr(loopHeadLabel);
// Put the implicit block back on the stack and yield the last expression to it
m_blockInfos.Push(implicitBlockInfo);
m_writer->MarkAsmJsLabel(loopTailLabel);
// Pop the implicit block to resolve the yield correctly
EmitInfo loopInfo = PopLabel(loopTailLabel);
m_writer->ExitLoop(loopId);
return loopInfo;
}
template<WasmOp wasmOp>
EmitInfo WasmBytecodeGenerator::EmitCall()
{
uint32 funcNum = Js::Constants::UninitializedValue;
uint32 signatureId = Js::Constants::UninitializedValue;
WasmSignature* calleeSignature = nullptr;
EmitInfo indirectIndexInfo;
const bool isImportCall = GetReader()->m_currentNode.call.funcType == FunctionIndexTypes::Import;
Assert(isImportCall || GetReader()->m_currentNode.call.funcType == FunctionIndexTypes::Function || GetReader()->m_currentNode.call.funcType == FunctionIndexTypes::ImportThunk);
switch (wasmOp)
{
case wbCall:
funcNum = GetReader()->m_currentNode.call.num;
calleeSignature = m_module->GetWasmFunctionInfo(funcNum)->GetSignature();
break;
case wbCallIndirect:
indirectIndexInfo = PopEvalStack(WasmTypes::I32, _u("Indirect call index must be int type"));
signatureId = GetReader()->m_currentNode.call.num;
calleeSignature = m_module->GetSignature(signatureId);
ReleaseLocation(&indirectIndexInfo);
break;
default:
Assume(UNREACHED);
}
const auto argOverflow = []
{
throw WasmCompilationException(_u("Argument size too big"));
};
// emit start call
Js::ArgSlot argSize;
Js::OpCodeAsmJs startCallOp;
if (isImportCall)
{
argSize = ArgSlotMath::Mul(calleeSignature->GetParamCount(), sizeof(Js::Var), argOverflow);
startCallOp = Js::OpCodeAsmJs::StartCall;
}
else
{
startCallOp = Js::OpCodeAsmJs::I_StartCall;
argSize = calleeSignature->GetParamsSize();
}
// Add return value
argSize = ArgSlotMath::Add(argSize, sizeof(Js::Var), argOverflow);
if (!Math::IsAligned<Js::ArgSlot>(argSize, sizeof(Js::Var)))
{
AssertMsg(UNREACHED, "Wasm argument size should always be Var aligned");
throw WasmCompilationException(_u("Internal Error"));
}
m_writer->AsmStartCall(startCallOp, argSize);
Js::ArgSlot nArgs = calleeSignature->GetParamCount();
// copy args into a list so they could be generated in the right order (FIFO)
EmitInfo* argsList = AnewArray(&m_alloc, EmitInfo, nArgs);
for (int i = int(nArgs) - 1; i >= 0; --i)
{
EmitInfo info = PopEvalStack(calleeSignature->GetParam((Js::ArgSlot)i), _u("Call argument does not match formal type"));
// We can release the location of the arguments now, because we won't create new temps between start/call
ReleaseLocation(&info);
argsList[i] = info;
}
// Skip the this pointer (aka undefined)
uint32 argLoc = 1;
for (Js::ArgSlot i = 0; i < nArgs; ++i)
{
EmitInfo info = argsList[i];
Js::OpCodeAsmJs argOp = Js::OpCodeAsmJs::Nop;
switch (info.type)
{
case WasmTypes::F32:
argOp = isImportCall ? Js::OpCodeAsmJs::ArgOut_Flt : Js::OpCodeAsmJs::I_ArgOut_Flt;
break;
case WasmTypes::F64:
argOp = isImportCall ? Js::OpCodeAsmJs::ArgOut_Db : Js::OpCodeAsmJs::I_ArgOut_Db;
break;
case WasmTypes::I32:
argOp = isImportCall ? Js::OpCodeAsmJs::ArgOut_Int : Js::OpCodeAsmJs::I_ArgOut_Int;
break;
case WasmTypes::I64:
argOp = isImportCall ? Js::OpCodeAsmJs::ArgOut_Long : Js::OpCodeAsmJs::I_ArgOut_Long;
break;
case WasmTypes::Any:
// In unreachable mode allow any type as argument since we won't actually emit the call
Assert(IsUnreachable());
if (IsUnreachable())
{
argOp = Js::OpCodeAsmJs::ArgOut_Int;
break;
}
// Fall through
default:
throw WasmCompilationException(_u("Unknown argument type %u"), info.type);
}
m_writer->AsmReg2(argOp, argLoc, info.location);
// Calculated next argument Js::Var location
if (isImportCall)
{
++argLoc;
}
else
{
const Js::ArgSlot currentArgSize = calleeSignature->GetParamSize(i);
Assert(Math::IsAligned<Js::ArgSlot>(currentArgSize, sizeof(Js::Var)));
argLoc += currentArgSize / sizeof(Js::Var);
}
}
AdeleteArray(&m_alloc, nArgs, argsList);
// emit call
switch (wasmOp)
{
case wbCall:
{
uint32 offset = isImportCall ? m_module->GetImportFuncOffset() : m_module->GetFuncOffset();
uint32 index = UInt32Math::Add(offset, funcNum);
m_writer->AsmSlot(Js::OpCodeAsmJs::LdSlot, 0, 1, index);
break;
}
case wbCallIndirect:
m_writer->AsmSlot(Js::OpCodeAsmJs::LdSlotArr, 0, 1, m_module->GetTableEnvironmentOffset());
m_writer->AsmSlot(Js::OpCodeAsmJs::LdArr_WasmFunc, 0, 0, indirectIndexInfo.location);
m_writer->AsmReg1IntConst1(Js::OpCodeAsmJs::CheckSignature, 0, calleeSignature->GetSignatureId());
break;
default:
Assume(UNREACHED);
}
// calculate number of RegSlots(Js::Var) the call consumes
Js::ArgSlot args;
Js::OpCodeAsmJs callOp = Js::OpCodeAsmJs::Nop;
if (isImportCall)
{
args = calleeSignature->GetParamCount();
ArgSlotMath::Inc(args, argOverflow);
callOp = Js::OpCodeAsmJs::Call;
}
else
{
Assert(Math::IsAligned<Js::ArgSlot>(argSize, sizeof(Js::Var)));
args = argSize / sizeof(Js::Var);
callOp = Js::OpCodeAsmJs::I_Call;
}
m_writer->AsmCall(callOp, 0, 0, args, WasmToAsmJs::GetAsmJsReturnType(calleeSignature->GetResultType()));
// emit result coercion
EmitInfo retInfo;
retInfo.type = calleeSignature->GetResultType();
if (retInfo.type != WasmTypes::Void)
{
Js::OpCodeAsmJs convertOp = Js::OpCodeAsmJs::Nop;
retInfo.location = GetRegisterSpace(retInfo.type)->AcquireTmpRegister();
switch (retInfo.type)
{
case WasmTypes::F32:
convertOp = isImportCall ? Js::OpCodeAsmJs::Conv_VTF : Js::OpCodeAsmJs::Ld_Flt;
break;
case WasmTypes::F64:
convertOp = isImportCall ? Js::OpCodeAsmJs::Conv_VTD : Js::OpCodeAsmJs::Ld_Db;
break;
case WasmTypes::I32:
convertOp = isImportCall ? Js::OpCodeAsmJs::Conv_VTI : Js::OpCodeAsmJs::Ld_Int;
break;
case WasmTypes::I64:
convertOp = isImportCall ? Js::OpCodeAsmJs::Conv_VTL : Js::OpCodeAsmJs::Ld_Long;
break;
default:
throw WasmCompilationException(_u("Unknown call return type %u"), retInfo.type);
}
m_writer->AsmReg2(convertOp, retInfo.location, 0);
}
// track stack requirements for out params
// + 1 for return address
uint32 maxDepthForLevel = args + 1;
if (maxDepthForLevel > m_maxArgOutDepth)
{
m_maxArgOutDepth = maxDepthForLevel;
}
return retInfo;
}
EmitInfo WasmBytecodeGenerator::EmitIfElseExpr()
{
Js::ByteCodeLabel falseLabel = m_writer->DefineLabel();
Js::ByteCodeLabel endLabel = m_writer->DefineLabel();
EmitInfo checkExpr = PopEvalStack(WasmTypes::I32, _u("If expression must have type i32"));
ReleaseLocation(&checkExpr);
m_writer->AsmBrReg1(Js::OpCodeAsmJs::BrFalse_Int, falseLabel, checkExpr.location);
BlockInfo blockInfo = PushLabel(endLabel);
bool endOnElse = false;
EmitBlockCommon(&blockInfo, &endOnElse);
EnsureYield(blockInfo);
m_writer->AsmBr(endLabel);
m_writer->MarkAsmJsLabel(falseLabel);
EmitInfo retInfo;
EmitInfo falseExpr;
if (endOnElse)
{
if (blockInfo.yieldInfo)
{
blockInfo.yieldInfo->didYield = false;
}
EmitBlockCommon(&blockInfo);
EnsureYield(blockInfo);
}
m_writer->MarkAsmJsLabel(endLabel);
return PopLabel(endLabel);
}
void WasmBytecodeGenerator::EmitBrTable()
{
const uint32 numTargets = GetReader()->m_currentNode.brTable.numTargets;
const uint32* targetTable = GetReader()->m_currentNode.brTable.targetTable;
const uint32 defaultEntry = GetReader()->m_currentNode.brTable.defaultTarget;
// Compile scrutinee
EmitInfo scrutineeInfo = PopEvalStack(WasmTypes::I32, _u("br_table expression must be of type i32"));
m_writer->AsmReg2(Js::OpCodeAsmJs::BeginSwitch_Int, scrutineeInfo.location, scrutineeInfo.location);
EmitInfo yieldInfo;
if (ShouldYieldToBlock(defaultEntry))
{
// If the scrutinee is any then check the stack before popping
if (scrutineeInfo.type == WasmTypes::Any && m_evalStack.Peek().type == WasmTypes::Limit)
{
yieldInfo = scrutineeInfo;
}
else
{
yieldInfo = PopEvalStack();
}
}
// Compile cases
for (uint32 i = 0; i < numTargets; i++)
{
uint32 target = targetTable[i];
YieldToBlock(target, yieldInfo);
Js::ByteCodeLabel targetLabel = GetLabel(target);
m_writer->AsmBrReg1Const1(Js::OpCodeAsmJs::Case_IntConst, targetLabel, scrutineeInfo.location, i);
}
YieldToBlock(defaultEntry, yieldInfo);
m_writer->AsmBr(GetLabel(defaultEntry), Js::OpCodeAsmJs::EndSwitch_Int);
ReleaseLocation(&scrutineeInfo);
ReleaseLocation(&yieldInfo);
SetUnreachableState(true);
}
EmitInfo WasmBytecodeGenerator::EmitGrowMemory()
{
SetUsesMemory(0);
EmitInfo info = PopEvalStack(WasmTypes::I32, _u("Invalid type for GrowMemory"));
m_writer->AsmReg2(Js::OpCodeAsmJs::GrowMemory, info.location, info.location);
return info;
}
EmitInfo WasmBytecodeGenerator::EmitDrop()
{
EmitInfo info = PopEvalStack();
ReleaseLocation(&info);
return EmitInfo();
}
EmitInfo WasmBytecodeGenerator::EmitBinExpr(Js::OpCodeAsmJs op, const WasmTypes::WasmType* signature)
{
WasmTypes::WasmType resultType = signature[0];
WasmTypes::WasmType lhsType = signature[1];
WasmTypes::WasmType rhsType = signature[2];
EmitInfo rhs = PopEvalStack(lhsType);
EmitInfo lhs = PopEvalStack(rhsType);
ReleaseLocation(&rhs);
ReleaseLocation(&lhs);
Js::RegSlot resultReg = GetRegisterSpace(resultType)->AcquireTmpRegister();
m_writer->AsmReg3(op, resultReg, lhs.location, rhs.location);
return EmitInfo(resultReg, resultType);
}
EmitInfo WasmBytecodeGenerator::EmitUnaryExpr(Js::OpCodeAsmJs op, const WasmTypes::WasmType* signature)
{
WasmTypes::WasmType resultType = signature[0];
WasmTypes::WasmType inputType = signature[1];
EmitInfo info = PopEvalStack(inputType);
ReleaseLocation(&info);
if (resultType == WasmTypes::Void)
{
m_writer->AsmReg2(op, 0, info.location);
return EmitInfo();
}
Js::RegSlot resultReg = GetRegisterSpace(resultType)->AcquireTmpRegister();
m_writer->AsmReg2(op, resultReg, info.location);
return EmitInfo(resultReg, resultType);
}
EmitInfo WasmBytecodeGenerator::EmitMemAccess(WasmOp wasmOp, const WasmTypes::WasmType* signature, Js::ArrayBufferView::ViewType viewType, bool isStore)
{
WasmTypes::WasmType type = signature[0];
SetUsesMemory(0);
const uint32 mask = Js::ArrayBufferView::ViewMask[viewType];
const uint32 alignment = GetReader()->m_currentNode.mem.alignment;
const uint32 offset = GetReader()->m_currentNode.mem.offset;
if ((mask << 1) & (1 << alignment))
{
throw WasmCompilationException(_u("alignment must not be larger than natural"));
}
EmitInfo rhsInfo;
if (isStore)
{
rhsInfo = PopEvalStack(type, _u("Invalid type for store op"));
}
EmitInfo exprInfo = PopEvalStack(WasmTypes::I32, _u("Index expression must be of type i32"));
if (isStore) // Stores
{
m_writer->WasmMemAccess(Js::OpCodeAsmJs::StArrWasm, rhsInfo.location, exprInfo.location, offset, viewType);
ReleaseLocation(&rhsInfo);
ReleaseLocation(&exprInfo);
return EmitInfo();
}
ReleaseLocation(&exprInfo);
Js::RegSlot resultReg = GetRegisterSpace(type)->AcquireTmpRegister();
m_writer->WasmMemAccess(Js::OpCodeAsmJs::LdArrWasm, resultReg, exprInfo.location, offset, viewType);
EmitInfo yieldInfo;
if (!isStore)
{
// Yield only on load
yieldInfo = EmitInfo(resultReg, type);
}
return yieldInfo;
}
void WasmBytecodeGenerator::EmitReturnExpr(EmitInfo* explicitRetInfo)
{
if (m_funcInfo->GetResultType() == WasmTypes::Void)
{
// TODO (michhol): consider moving off explicit 0 for return reg
m_writer->AsmReg1(Js::OpCodeAsmJs::LdUndef, 0);
}
else
{
EmitInfo retExprInfo = explicitRetInfo ? *explicitRetInfo : PopEvalStack();
if (retExprInfo.type != WasmTypes::Any && m_funcInfo->GetResultType() != retExprInfo.type)
{
throw WasmCompilationException(_u("Result type must match return type"));
}
Js::OpCodeAsmJs retOp = GetReturnOp(retExprInfo.type);
m_writer->Conv(retOp, 0, retExprInfo.location);
ReleaseLocation(&retExprInfo);
}
m_writer->AsmBr(m_funcInfo->GetExitLabel());
SetUnreachableState(true);
}
EmitInfo WasmBytecodeGenerator::EmitSelect()
{
EmitInfo conditionInfo = PopEvalStack(WasmTypes::I32, _u("select condition must have i32 type"));
EmitInfo falseInfo = PopEvalStack();
EmitInfo trueInfo = PopEvalStack(falseInfo.type, _u("select operands must both have same type"));
ReleaseLocation(&conditionInfo);
ReleaseLocation(&falseInfo);
ReleaseLocation(&trueInfo);
if (IsUnreachable())
{
if (trueInfo.type == WasmTypes::Any)
{
// Report the type of falseInfo for type checking
return EmitInfo(falseInfo.type);
}
// Otherwise report the type of trueInfo for type checking
return EmitInfo(trueInfo.type);
}
WasmTypes::WasmType selectType = trueInfo.type;
EmitInfo resultInfo = EmitInfo(GetRegisterSpace(selectType)->AcquireTmpRegister(), selectType);
Js::ByteCodeLabel falseLabel = m_writer->DefineLabel();
Js::ByteCodeLabel doneLabel = m_writer->DefineLabel();
Js::OpCodeAsmJs loadOp = GetLoadOp(resultInfo.type);
// var result;
// if (!condition) goto:condFalse
// result = trueRes;
// goto:done;
//:condFalse
// result = falseRes;
//:done
m_writer->AsmBrReg1(Js::OpCodeAsmJs::BrFalse_Int, falseLabel, conditionInfo.location);
m_writer->AsmReg2(loadOp, resultInfo.location, trueInfo.location);
m_writer->AsmBr(doneLabel);
m_writer->MarkAsmJsLabel(falseLabel);
m_writer->AsmReg2(loadOp, resultInfo.location, falseInfo.location);
m_writer->MarkAsmJsLabel(doneLabel);
return resultInfo;
}
void WasmBytecodeGenerator::EmitBr()
{
uint32 depth = GetReader()->m_currentNode.br.depth;
if (ShouldYieldToBlock(depth))
{
EmitInfo info = PopEvalStack();
YieldToBlock(depth, info);
ReleaseLocation(&info);
}
Js::ByteCodeLabel target = GetLabel(depth);
m_writer->AsmBr(target);
SetUnreachableState(true);
}
EmitInfo WasmBytecodeGenerator::EmitBrIf()
{
uint32 depth = GetReader()->m_currentNode.br.depth;
EmitInfo conditionInfo = PopEvalStack(WasmTypes::I32, _u("br_if condition must have i32 type"));
ReleaseLocation(&conditionInfo);
EmitInfo info;
if (ShouldYieldToBlock(depth))
{
info = PopEvalStack();
YieldToBlock(depth, info);
}
Js::ByteCodeLabel target = GetLabel(depth);
m_writer->AsmBrReg1(Js::OpCodeAsmJs::BrTrue_Int, target, conditionInfo.location);
return info;
}
Js::OpCodeAsmJs WasmBytecodeGenerator::GetLoadOp(WasmTypes::WasmType wasmType)
{
switch (wasmType)
{
case WasmTypes::F32:
return Js::OpCodeAsmJs::Ld_Flt;
case WasmTypes::F64:
return Js::OpCodeAsmJs::Ld_Db;
case WasmTypes::I32:
return Js::OpCodeAsmJs::Ld_Int;
case WasmTypes::I64:
return Js::OpCodeAsmJs::Ld_Long;
case WasmTypes::Any:
// In unreachable mode load the any type like an int since we won't actually emit the load
Assert(IsUnreachable());
if (IsUnreachable())
{
return Js::OpCodeAsmJs::Ld_Int;
}
default:
throw WasmCompilationException(_u("Unknown load operator %u"), wasmType);
}
}
Js::OpCodeAsmJs WasmBytecodeGenerator::GetReturnOp(WasmTypes::WasmType type)
{
Js::OpCodeAsmJs retOp = Js::OpCodeAsmJs::Nop;
switch (type)
{
case WasmTypes::F32:
retOp = Js::OpCodeAsmJs::Return_Flt;
break;
case WasmTypes::F64:
retOp = Js::OpCodeAsmJs::Return_Db;
break;
case WasmTypes::I32:
retOp = Js::OpCodeAsmJs::Return_Int;
break;
case WasmTypes::I64:
retOp = Js::OpCodeAsmJs::Return_Long;
break;
case WasmTypes::Any:
// In unreachable mode load the any type like an int since we won't actually emit the load
Assert(IsUnreachable());
if (IsUnreachable())
{
return Js::OpCodeAsmJs::Return_Int;
}
default:
throw WasmCompilationException(_u("Unknown return type %u"), type);
}
return retOp;
}
void WasmBytecodeGenerator::ReleaseLocation(EmitInfo* info)
{
if (WasmTypes::IsLocalType(info->type))
{
GetRegisterSpace(info->type)->ReleaseLocation(info);
}
}
EmitInfo WasmBytecodeGenerator::EnsureYield(BlockInfo info)
{
EmitInfo yieldEmitInfo;
if (info.HasYield())
{
yieldEmitInfo = info.yieldInfo->info;
if (!info.DidYield())
{
// Emit a load to the yield location to make sure we have a dest there
// Most likely we can't reach this code so the value doesn't matter
info.yieldInfo->didYield = true;
EmitLoadConst(yieldEmitInfo, GetZeroCnst());
}
}
return yieldEmitInfo;
}
EmitInfo WasmBytecodeGenerator::PopLabel(Js::ByteCodeLabel labelValidation)
{
Assert(m_blockInfos.Count() > 0);
BlockInfo info = m_blockInfos.Pop();
UNREFERENCED_PARAMETER(labelValidation);
Assert(info.label == labelValidation);
return EnsureYield(info);
}
BlockInfo WasmBytecodeGenerator::PushLabel(Js::ByteCodeLabel label, bool addBlockYieldInfo /*= true*/)
{
BlockInfo info;
info.label = label;
if (addBlockYieldInfo)
{
WasmTypes::WasmType type = GetReader()->m_currentNode.block.sig;
if (type != WasmTypes::Void)
{
info.yieldInfo = Anew(&m_alloc, BlockInfo::YieldInfo);
info.yieldInfo->info = EmitInfo(GetRegisterSpace(type)->AcquireTmpRegister(), type);
info.yieldInfo->didYield = false;
}
}
m_blockInfos.Push(info);
return info;
}
void WasmBytecodeGenerator::YieldToBlock(uint32 relativeDepth, EmitInfo expr)
{
BlockInfo blockInfo = GetBlockInfo(relativeDepth);
YieldToBlock(blockInfo, expr);
}
void WasmBytecodeGenerator::YieldToBlock(BlockInfo blockInfo, EmitInfo expr)
{
if (blockInfo.HasYield() && expr.type != WasmTypes::Any)
{
EmitInfo yieldInfo = blockInfo.yieldInfo->info;
if (yieldInfo.type != expr.type)
{
throw WasmCompilationException(_u("Invalid yield type"));
}
if (!IsUnreachable())
{
blockInfo.yieldInfo->didYield = true;
m_writer->AsmReg2(GetLoadOp(expr.type), yieldInfo.location, expr.location);
}
}
}
bool WasmBytecodeGenerator::ShouldYieldToBlock(uint32 relativeDepth) const
{
return GetBlockInfo(relativeDepth).HasYield();
}
Wasm::BlockInfo WasmBytecodeGenerator::GetBlockInfo(uint32 relativeDepth) const
{
if (relativeDepth >= (uint32)m_blockInfos.Count())
{
throw WasmCompilationException(_u("Invalid branch target"));
}
return m_blockInfos.Peek(relativeDepth);
}
Js::ByteCodeLabel WasmBytecodeGenerator::GetLabel(uint32 relativeDepth)
{
return GetBlockInfo(relativeDepth).label;
}
WasmRegisterSpace* WasmBytecodeGenerator::GetRegisterSpace(WasmTypes::WasmType type)
{
switch (type)
{
case WasmTypes::I32: return mTypedRegisterAllocator.GetRegisterSpace(WAsmJs::INT32);
case WasmTypes::I64: return mTypedRegisterAllocator.GetRegisterSpace(WAsmJs::INT64);
case WasmTypes::F32: return mTypedRegisterAllocator.GetRegisterSpace(WAsmJs::FLOAT32);
case WasmTypes::F64: return mTypedRegisterAllocator.GetRegisterSpace(WAsmJs::FLOAT64);
default:
return nullptr;
}
}
EmitInfo WasmBytecodeGenerator::PopEvalStack(WasmTypes::WasmType expectedType, const char16* mismatchMessage)
{
// The scope marker should at least be there
Assert(!m_evalStack.Empty());
EmitInfo info = m_evalStack.Pop();
if (info.type == WasmTypes::Limit)
{
throw WasmCompilationException(_u("Reached end of stack"));
}
if (expectedType != WasmTypes::Any &&
info.type != WasmTypes::Any &&
info.type != expectedType)
{
if (!mismatchMessage)
{
mismatchMessage = _u("Type mismatch");
}
throw WasmCompilationException(mismatchMessage);
}
Assert(info.type != WasmTypes::Any || IsUnreachable());
return info;
}
void WasmBytecodeGenerator::PushEvalStack(EmitInfo info)
{
Assert(!m_evalStack.Empty());
m_evalStack.Push(info);
}
void WasmBytecodeGenerator::EnterEvalStackScope()
{
m_evalStack.Push(EmitInfo(WasmTypes::Limit));
}
void WasmBytecodeGenerator::ExitEvalStackScope()
{
Assert(!m_evalStack.Empty());
EmitInfo info = m_evalStack.Pop();
// It is possible to have unconsumed Any type left on the stack, simply remove them
while (info.type == WasmTypes::Any)
{
Assert(!m_evalStack.Empty());
info = m_evalStack.Pop();
}
if (info.type != WasmTypes::Limit)
{
uint32 nElemLeftOnStack = 1;
while(m_evalStack.Pop().type != WasmTypes::Limit) { ++nElemLeftOnStack; }
throw WasmCompilationException(_u("Expected stack to be empty, but has %d"), nElemLeftOnStack);
}
}
void WasmBytecodeGenerator::SetUnreachableState(bool isUnreachable)
{
m_writer = isUnreachable ? m_emptyWriter : m_originalWriter;
if (isUnreachable)
{
// Replace the current stack with the any type
Assert(!m_evalStack.Empty());
uint32 popped = 0;
while (m_evalStack.Top().type != WasmTypes::Limit)
{
EmitInfo info = m_evalStack.Pop();
ReleaseLocation(&info);
++popped;
}
while (popped-- > 0)
{
m_evalStack.Push(EmitInfo(WasmTypes::Any));
}
}
this->isUnreachable = isUnreachable;
}
void WasmBytecodeGenerator::SetUsesMemory(uint32 memoryIndex)
{
// Only support one memory at this time
Assert(memoryIndex == 0);
if (!m_module->HasMemory() && !m_module->HasMemoryImport())
{
throw WasmCompilationException(_u("unknown memory"));
}
GetFunctionBody()->GetAsmJsFunctionInfo()->SetUsesHeapBuffer(true);
}
Wasm::WasmReaderBase* WasmBytecodeGenerator::GetReader() const
{
if (m_funcInfo->GetCustomReader())
{
return m_funcInfo->GetCustomReader();
}
return m_module->GetReader();
}
void WasmCompilationException::FormatError(const char16* _msg, va_list arglist)
{
char16 buf[2048];
_vsnwprintf_s(buf, _countof(buf), _TRUNCATE, _msg, arglist);
errorMsg = SysAllocString(buf);
}
WasmCompilationException::WasmCompilationException(const char16* _msg, ...) : errorMsg(nullptr)
{
va_list arglist;
va_start(arglist, _msg);
FormatError(_msg, arglist);
}
WasmCompilationException::WasmCompilationException(const char16* _msg, va_list arglist) : errorMsg(nullptr)
{
FormatError(_msg, arglist);
}
} // namespace Wasm
#endif // ENABLE_WASM