//-------------------------------------------------------------------------------------------------------
// 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
