| //------------------------------------------------------------------------------------------------------- |
| // 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 "WasmLimits.h" |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| #include "Codex/Utf8Helper.h" |
| #endif |
| |
| namespace Wasm |
| { |
| |
| namespace WasmTypes |
| { |
| bool IsLocalType(WasmTypes::WasmType type) |
| { |
| // Check if type in range ]Void,Limit[ |
| return (uint32)(type - 1) < (WasmTypes::Limit - 1); |
| } |
| |
| uint32 GetTypeByteSize(WasmType type) |
| { |
| switch (type) |
| { |
| case Void: return sizeof(Js::Var); |
| case I32: return sizeof(int32); |
| case I64: return sizeof(int64); |
| case F32: return sizeof(float); |
| case F64: return sizeof(double); |
| default: |
| Js::Throw::InternalError(); |
| } |
| } |
| |
| const char16 * GetTypeName(WasmType type) |
| { |
| const char16* typestring = _u("unknown"); |
| switch (type) { |
| case WasmTypes::WasmType::Void: |
| typestring = _u("void"); |
| break; |
| case WasmTypes::WasmType::I32: |
| typestring = _u("i32"); |
| break; |
| case WasmTypes::WasmType::I64: |
| typestring = _u("i64"); |
| break; |
| case WasmTypes::WasmType::F32: |
| typestring = _u("f32"); |
| break; |
| case WasmTypes::WasmType::F64: |
| typestring = _u("f64"); |
| break; |
| default: |
| Assert(false); |
| break; |
| } |
| return typestring; |
| } |
| |
| } // namespace WasmTypes |
| |
| WasmTypes::WasmType LanguageTypes::ToWasmType(int8 binType) |
| { |
| switch (binType) |
| { |
| case LanguageTypes::i32: return WasmTypes::I32; |
| case LanguageTypes::i64: return WasmTypes::I64; |
| case LanguageTypes::f32: return WasmTypes::F32; |
| case LanguageTypes::f64: return WasmTypes::F64; |
| default: |
| throw WasmCompilationException(_u("Invalid binary type %d"), binType); |
| } |
| } |
| |
| bool FunctionIndexTypes::CanBeExported(FunctionIndexTypes::Type funcType) |
| { |
| return funcType == FunctionIndexTypes::Function || funcType == FunctionIndexTypes::ImportThunk; |
| } |
| |
| WasmBinaryReader::WasmBinaryReader(ArenaAllocator* alloc, Js::WebAssemblyModule* module, const byte* source, size_t length) : |
| m_module(module), |
| m_curFuncEnd(nullptr), |
| m_alloc(alloc), |
| m_readerState(READER_STATE_UNKNOWN) |
| { |
| m_start = m_pc = source; |
| m_end = source + length; |
| m_currentSection.code = bSectLimit; |
| #if DBG_DUMP |
| m_ops = Anew(m_alloc, OpSet, m_alloc); |
| #endif |
| } |
| |
| void WasmBinaryReader::InitializeReader() |
| { |
| ValidateModuleHeader(); |
| m_readerState = READER_STATE_UNKNOWN; |
| } |
| |
| void WasmBinaryReader::ThrowDecodingError(const char16* msg, ...) |
| { |
| va_list argptr; |
| va_start(argptr, msg); |
| throw WasmCompilationException(msg, argptr); |
| } |
| |
| SectionHeader WasmBinaryReader::ReadNextSection() |
| { |
| while (true) |
| { |
| if (EndOfModule()) |
| { |
| memset(&m_currentSection, 0, sizeof(SectionHeader)); |
| m_currentSection.code = bSectLimit; |
| return m_currentSection; |
| } |
| |
| m_currentSection = ReadSectionHeader(); |
| if (SectionInfo::All[m_currentSection.code].flag == fSectIgnore) |
| { |
| TRACE_WASM_SECTION(_u("Ignore this section")); |
| m_pc = m_currentSection.end; |
| // Read next section |
| continue; |
| } |
| |
| return m_currentSection; |
| } |
| } |
| |
| bool WasmBinaryReader::ProcessCurrentSection() |
| { |
| Assert(m_currentSection.code != bSectLimit); |
| TRACE_WASM_SECTION(_u("Process section %s"), SectionInfo::All[m_currentSection.code].name); |
| m_readerState = READER_STATE_MODULE; |
| |
| switch (m_currentSection.code) |
| { |
| case bSectMemory: |
| ReadMemorySection(false); |
| break; |
| case bSectType: |
| ReadSignatureTypeSection(); |
| break; |
| case bSectImport: |
| ReadImportSection(); |
| break; |
| case bSectFunction: |
| ReadFunctionSignatures(); |
| break; |
| case bSectFunctionBodies: |
| ReadFunctionHeaders(); |
| break; |
| case bSectExport: |
| ReadExportSection(); |
| break; |
| case bSectStartFunction: |
| ReadStartFunction(); |
| break; |
| case bSectData: |
| ReadDataSection(); |
| break; |
| case bSectTable: |
| ReadTableSection(false); |
| break; |
| case bSectElement: |
| ReadElementSection(); |
| break; |
| case bSectName: |
| ReadNameSection(); |
| break; |
| case bSectGlobal: |
| ReadGlobalSection(); |
| break; |
| case bSectCustom: |
| ReadCustomSection(); |
| break; |
| default: |
| Assert(UNREACHED); |
| m_readerState = READER_STATE_UNKNOWN; |
| return false; |
| } |
| |
| m_readerState = READER_STATE_UNKNOWN; |
| |
| return m_pc == m_currentSection.end; |
| } |
| |
| SectionHeader WasmBinaryReader::ReadSectionHeader() |
| { |
| SectionHeader header; |
| header.start = m_pc; |
| header.code = bSectLimit; |
| |
| uint32 len = 0; |
| CompileAssert(sizeof(SectionCode) == sizeof(uint8)); |
| SectionCode sectionId = (SectionCode)ReadVarUInt7(); |
| |
| if (sectionId > bsectLastKnownSection) |
| { |
| ThrowDecodingError(_u("Invalid known section opcode %u"), sectionId); |
| } |
| |
| uint32 sectionSize = LEB128(len); |
| header.end = m_pc + sectionSize; |
| CheckBytesLeft(sectionSize); |
| |
| header.code = sectionId; |
| if (sectionId == bSectCustom) |
| { |
| header.name = ReadInlineName(len, header.nameLength); |
| } |
| else |
| { |
| header.name = SectionInfo::All[sectionId].name; |
| header.nameLength = SectionInfo::All[sectionId].nameLength; |
| } |
| |
| TRACE_WASM_SECTION(_u("Section Header: %s, length = %u (0x%x)"), header.name, sectionSize, sectionSize); |
| return header; |
| } |
| |
| #if DBG_DUMP |
| void WasmBinaryReader::PrintOps() |
| { |
| int count = m_ops->Count(); |
| if (count == 0) |
| { |
| return; |
| } |
| WasmOp* ops = HeapNewArray(WasmOp, count); |
| |
| auto iter = m_ops->GetIterator(); |
| int i = 0; |
| while (iter.IsValid()) |
| { |
| ops[i] = iter.CurrentKey(); |
| iter.MoveNext(); |
| ++i; |
| } |
| for (i = 0; i < count; ++i) |
| { |
| int j = i; |
| while (j > 0 && ops[j-1] > ops[j]) |
| { |
| WasmOp tmp = ops[j]; |
| ops[j] = ops[j - 1]; |
| ops[j - 1] = tmp; |
| |
| --j; |
| } |
| } |
| for (i = 0; i < count; ++i) |
| { |
| switch (ops[i]) |
| { |
| #define WASM_OPCODE(opname, opcode, sig, nyi) \ |
| case opcode: \ |
| Output::Print(_u("%s\r\n"), _u(#opname)); \ |
| break; |
| #include "WasmBinaryOpCodes.h" |
| } |
| } |
| HeapDeleteArray(count, ops); |
| } |
| |
| #endif |
| |
| void WasmBinaryReader::ReadFunctionHeaders() |
| { |
| uint32 len; |
| uint32 entries = LEB128(len); |
| uint32 importCount = m_module->GetImportedFunctionCount(); |
| if (m_module->GetWasmFunctionCount() < importCount || |
| entries != m_module->GetWasmFunctionCount() - importCount) |
| { |
| ThrowDecodingError(_u("Function signatures and function bodies count mismatch")); |
| } |
| |
| for (uint32 i = 0; i < entries; ++i) |
| { |
| uint32 funcIndex = i + importCount; |
| WasmFunctionInfo* funcInfo = m_module->GetWasmFunctionInfo(funcIndex); |
| |
| const uint32 funcSize = LEB128(len); |
| if (funcSize > Limits::GetMaxFunctionSize()) |
| { |
| ThrowDecodingError(_u("Function body too big")); |
| } |
| funcInfo->m_readerInfo.size = funcSize; |
| funcInfo->m_readerInfo.startOffset = (m_pc - m_start); |
| CheckBytesLeft(funcSize); |
| TRACE_WASM_DECODER(_u("Function body header: index = %u, size = %u"), funcIndex, funcSize); |
| const byte* end = m_pc + funcSize; |
| m_pc = end; |
| } |
| } |
| |
| void WasmBinaryReader::SeekToFunctionBody(class WasmFunctionInfo* funcInfo) |
| { |
| FunctionBodyReaderInfo readerInfo = funcInfo->m_readerInfo; |
| if (readerInfo.startOffset >= (m_end - m_start)) |
| { |
| ThrowDecodingError(_u("Function byte offset out of bounds")); |
| } |
| if (m_readerState != READER_STATE_UNKNOWN) |
| { |
| ThrowDecodingError(_u("Wasm reader in an invalid state to read function code")); |
| } |
| m_readerState = READER_STATE_FUNCTION; |
| |
| // Seek to the function start and skip function header (count) |
| m_pc = m_start + readerInfo.startOffset; |
| m_funcState.size = readerInfo.size; |
| m_funcState.count = 0; |
| CheckBytesLeft(readerInfo.size); |
| m_curFuncEnd = m_pc + m_funcState.size; |
| |
| uint32 length = 0; |
| uint32 numLocalsEntries = LEB128(length); |
| m_funcState.count += length; |
| |
| // locals |
| for (uint32 j = 0; j < numLocalsEntries; j++) |
| { |
| uint32 numLocals = LEB128(length); |
| m_funcState.count += length; |
| WasmTypes::WasmType type = ReadWasmType(length); |
| if (!WasmTypes::IsLocalType(type)) |
| { |
| ThrowDecodingError(_u("Invalid local type")); |
| } |
| m_funcState.count += length; |
| |
| uint32 totalLocals = 0; |
| if (UInt32Math::Add(funcInfo->GetLocalCount(), numLocals, &totalLocals) || totalLocals > Limits::GetMaxFunctionLocals()) |
| { |
| ThrowDecodingError(_u("Too many locals")); |
| } |
| funcInfo->AddLocal(type, numLocals); |
| TRACE_WASM_DECODER(_u("Local: type = %s, count = %u"), WasmTypes::GetTypeName(type), numLocals); |
| } |
| } |
| |
| void WasmBinaryReader::FunctionEnd() |
| { |
| m_readerState = READER_STATE_UNKNOWN; |
| } |
| |
| bool WasmBinaryReader::IsCurrentFunctionCompleted() const |
| { |
| return m_pc == m_curFuncEnd; |
| } |
| |
| WasmOp WasmBinaryReader::ReadExpr() |
| { |
| WasmOp op = m_currentNode.op = (WasmOp)*m_pc++; |
| ++m_funcState.count; |
| |
| if (EndOfFunc()) |
| { |
| // end of AST |
| if (op != wbEnd) |
| { |
| ThrowDecodingError(_u("missing function end opcode")); |
| } |
| return op; |
| } |
| |
| switch (op) |
| { |
| case wbBlock: |
| case wbLoop: |
| case wbIf: |
| BlockNode(); |
| break; |
| case wbElse: |
| // no node attributes |
| break; |
| case wbCall: |
| CallNode(); |
| break; |
| case wbCallIndirect: |
| CallIndirectNode(); |
| break; |
| case wbBr: |
| case wbBrIf: |
| BrNode(); |
| break; |
| case wbBrTable: |
| BrTableNode(); |
| break; |
| case wbReturn: |
| break; |
| case wbI32Const: |
| ConstNode<WasmTypes::I32>(); |
| break; |
| case wbI64Const: |
| ConstNode<WasmTypes::I64>(); |
| break; |
| case wbF32Const: |
| ConstNode<WasmTypes::F32>(); |
| break; |
| case wbF64Const: |
| ConstNode<WasmTypes::F64>(); |
| break; |
| case wbSetLocal: |
| case wbGetLocal: |
| case wbTeeLocal: |
| case wbGetGlobal: |
| case wbSetGlobal: |
| VarNode(); |
| break; |
| case wbDrop: |
| break; |
| case wbEnd: |
| break; |
| case wbNop: |
| break; |
| case wbCurrentMemory: |
| case wbGrowMemory: |
| // Reserved value currently unused |
| ReadConst<uint8>(); |
| break; |
| #define WASM_MEM_OPCODE(opname, opcode, sig, nyi) \ |
| case wb##opname: \ |
| MemNode(); \ |
| break; |
| #include "WasmBinaryOpCodes.h" |
| default: |
| break; |
| } |
| |
| #if DBG_DUMP |
| m_ops->AddNew(op); |
| #endif |
| return op; |
| } |
| |
| void WasmBinaryReader::ValidateModuleHeader() |
| { |
| uint32 bytesLeft = (uint32)(m_end - m_pc); |
| if (bytesLeft > Limits::GetMaxModuleSize()) |
| { |
| ThrowDecodingError(_u("Module too big")); |
| } |
| |
| uint32 magicNumber = ReadConst<uint32>(); |
| uint32 version = ReadConst<uint32>(); |
| TRACE_WASM_DECODER(_u("Module Header: Magic 0x%x, Version %u"), magicNumber, version); |
| if (magicNumber != 0x6d736100) |
| { |
| ThrowDecodingError(_u("Malformed WASM module header!")); |
| } |
| |
| if (CONFIG_FLAG(WasmCheckVersion)) |
| { |
| // Accept version 0xd to avoid problem in our test infrastructure |
| // We should eventually remove support for 0xd. |
| // The Assert is here as a reminder in case we change the binary version and we haven't removed 0xd support yet |
| CompileAssert(binaryVersion == 0x1); |
| |
| if (version != binaryVersion && version != 0xd) |
| { |
| ThrowDecodingError(_u("Invalid WASM version!")); |
| } |
| } |
| } |
| |
| void WasmBinaryReader::CallNode() |
| { |
| uint32 length = 0; |
| |
| uint32 funcNum = LEB128(length); |
| m_funcState.count += length; |
| FunctionIndexTypes::Type funcType = m_module->GetFunctionIndexType(funcNum); |
| if (funcType == FunctionIndexTypes::Invalid) |
| { |
| ThrowDecodingError(_u("Function is out of bound")); |
| } |
| m_currentNode.call.funcType = funcType; |
| m_currentNode.call.num = funcNum; |
| } |
| |
| void WasmBinaryReader::CallIndirectNode() |
| { |
| uint32 length = 0; |
| |
| uint32 funcNum = LEB128(length); |
| // Reserved value currently unused |
| ReadConst<uint8>(); |
| if (!m_module->HasTable() && !m_module->HasTableImport()) |
| { |
| ThrowDecodingError(_u("Found call_indirect operator, but no table")); |
| } |
| |
| m_funcState.count += length; |
| if (funcNum >= m_module->GetSignatureCount()) |
| { |
| ThrowDecodingError(_u("Function is out of bound")); |
| } |
| m_currentNode.call.num = funcNum; |
| m_currentNode.call.funcType = FunctionIndexTypes::Function; |
| } |
| |
| void WasmBinaryReader::BlockNode() |
| { |
| int8 blockType = ReadConst<int8>(); |
| m_funcState.count++; |
| m_currentNode.block.sig = blockType == LanguageTypes::emptyBlock ? WasmTypes::Void : LanguageTypes::ToWasmType(blockType); |
| } |
| |
| // control flow |
| void WasmBinaryReader::BrNode() |
| { |
| uint32 len = 0; |
| m_currentNode.br.depth = LEB128(len); |
| m_funcState.count += len; |
| } |
| |
| void WasmBinaryReader::BrTableNode() |
| { |
| uint32 len = 0; |
| m_currentNode.brTable.numTargets = LEB128(len); |
| if (m_currentNode.brTable.numTargets > Limits::GetMaxBrTableElems()) |
| { |
| ThrowDecodingError(_u("br_table too big")); |
| } |
| m_funcState.count += len; |
| m_currentNode.brTable.targetTable = AnewArray(m_alloc, uint32, m_currentNode.brTable.numTargets); |
| |
| for (uint32 i = 0; i < m_currentNode.brTable.numTargets; i++) |
| { |
| m_currentNode.brTable.targetTable[i] = LEB128(len); |
| m_funcState.count += len; |
| } |
| m_currentNode.brTable.defaultTarget = LEB128(len); |
| m_funcState.count += len; |
| } |
| |
| void WasmBinaryReader::MemNode() |
| { |
| uint32 len = 0; |
| |
| // flags |
| const uint32 flags = LEB128(len); |
| m_currentNode.mem.alignment = (uint8)flags; |
| m_funcState.count += len; |
| |
| m_currentNode.mem.offset = LEB128(len); |
| m_funcState.count += len; |
| } |
| |
| // Locals/Globals |
| void WasmBinaryReader::VarNode() |
| { |
| uint32 length; |
| m_currentNode.var.num = LEB128(length); |
| m_funcState.count += length; |
| } |
| |
| // Const |
| template <WasmTypes::WasmType localType> |
| void WasmBinaryReader::ConstNode() |
| { |
| uint32 len = 0; |
| switch (localType) |
| { |
| case WasmTypes::I32: |
| m_currentNode.cnst.i32 = SLEB128(len); |
| m_funcState.count += len; |
| break; |
| case WasmTypes::I64: |
| m_currentNode.cnst.i64 = SLEB128<int64>(len); |
| m_funcState.count += len; |
| break; |
| case WasmTypes::F32: |
| m_currentNode.cnst.f32 = ReadConst<float>(); |
| m_funcState.count += sizeof(float); |
| break; |
| case WasmTypes::F64: |
| m_currentNode.cnst.f64 = ReadConst<double>(); |
| m_funcState.count += sizeof(double); |
| break; |
| } |
| } |
| |
| bool WasmBinaryReader::EndOfFunc() |
| { |
| return m_funcState.count >= m_funcState.size; |
| } |
| |
| bool WasmBinaryReader::EndOfModule() |
| { |
| return (m_pc >= m_end); |
| } |
| |
| void WasmBinaryReader::ReadMemorySection(bool isImportSection) |
| { |
| uint32 length = 0; |
| uint32 count; |
| if (isImportSection) |
| { |
| count = 1; |
| } |
| else |
| { |
| count = LEB128(length); |
| } |
| if (count > 1) |
| { |
| ThrowDecodingError(_u("Maximum of 1 memory allowed")); |
| } |
| |
| if (count == 1) |
| { |
| SectionLimits limits = ReadSectionLimits(Limits::GetMaxMemoryInitialPages(), Limits::GetMaxMemoryMaximumPages(), _u("memory size too big")); |
| m_module->InitializeMemory(limits.initial, limits.maximum); |
| } |
| } |
| |
| void WasmBinaryReader::ReadSignatureTypeSection() |
| { |
| uint32 len = 0; |
| const uint32 numTypes = LEB128(len); |
| if (numTypes > Limits::GetMaxTypes()) |
| { |
| ThrowDecodingError(_u("Too many signatures")); |
| } |
| |
| m_module->SetSignatureCount(numTypes); |
| // signatures table |
| for (uint32 i = 0; i < numTypes; i++) |
| { |
| TRACE_WASM_DECODER(_u("Signature #%u"), i); |
| |
| WasmSignature* sig = m_module->GetSignature(i); |
| sig->SetSignatureId(i); |
| int8 form = ReadConst<int8>(); |
| if (form != LanguageTypes::func) |
| { |
| ThrowDecodingError(_u("Unexpected type form 0x%X"), form); |
| } |
| |
| uint32 paramCount32 = LEB128(len); |
| if (paramCount32 > Limits::GetMaxFunctionParams() || paramCount32 > UINT16_MAX) |
| { |
| ThrowDecodingError(_u("Too many arguments in signature")); |
| } |
| |
| Js::ArgSlot paramCount = (Js::ArgSlot)paramCount32; |
| sig->AllocateParams(paramCount, m_module->GetRecycler()); |
| for (Js::ArgSlot j = 0; j < paramCount; j++) |
| { |
| WasmTypes::WasmType type = ReadWasmType(len); |
| sig->SetParam(type, j); |
| } |
| |
| uint32 resultCount = LEB128(len); |
| if (resultCount > 1) |
| { |
| ThrowDecodingError(_u("Too many returns in signature: %u. Maximum allowed: 1"), resultCount); |
| } |
| if (resultCount == 1) |
| { |
| WasmTypes::WasmType type = ReadWasmType(len); |
| sig->SetResultType(type); |
| } |
| sig->FinalizeSignature(); |
| } |
| } |
| |
| void WasmBinaryReader::ReadFunctionSignatures() |
| { |
| uint32 len = 0; |
| uint32 nFunctions = LEB128(len); |
| |
| uint32 totalFunctions = 0; |
| if (UInt32Math::Add(nFunctions, m_module->GetWasmFunctionCount(), &totalFunctions) || totalFunctions > Limits::GetMaxFunctions()) |
| { |
| ThrowDecodingError(_u("Too many functions")); |
| } |
| |
| for (uint32 iFunc = 0; iFunc < nFunctions; iFunc++) |
| { |
| uint32 sigIndex = LEB128(len); |
| if (sigIndex >= m_module->GetSignatureCount()) |
| { |
| ThrowDecodingError(_u("Function signature is out of bound")); |
| } |
| |
| WasmSignature* sig = m_module->GetSignature(sigIndex); |
| m_module->AddWasmFunctionInfo(sig); |
| } |
| } |
| |
| void WasmBinaryReader::ReadExportSection() |
| { |
| uint32 length; |
| uint32 numExports = LEB128(length); |
| if (numExports > Limits::GetMaxExports()) |
| { |
| ThrowDecodingError(_u("Too many exports")); |
| } |
| |
| m_module->AllocateFunctionExports(numExports); |
| |
| ArenaAllocator tmpAlloc(_u("ExportDupCheck"), m_module->GetScriptContext()->GetThreadContext()->GetPageAllocator(), Js::Throw::OutOfMemory); |
| typedef SList<const char16*> NameList; |
| JsUtil::BaseDictionary<uint32, NameList*, ArenaAllocator> exportsNameDict(&tmpAlloc); |
| |
| for (uint32 iExport = 0; iExport < numExports; iExport++) |
| { |
| uint32 nameLength; |
| const char16* exportName = ReadInlineName(length, nameLength); |
| |
| // Check if the name is already used |
| NameList* list; |
| if (exportsNameDict.TryGetValue(nameLength, &list)) |
| { |
| const char16** found = list->Find([exportName, nameLength](const char16* existing) { |
| return wcsncmp(exportName, existing, nameLength) == 0; |
| }); |
| if (found) |
| { |
| ThrowDecodingError(_u("Duplicate export name: %s"), exportName); |
| } |
| } |
| else |
| { |
| list = Anew(&tmpAlloc, NameList, &tmpAlloc); |
| exportsNameDict.Add(nameLength, list); |
| } |
| list->Push(exportName); |
| |
| ExternalKinds::ExternalKind kind = (ExternalKinds::ExternalKind)ReadConst<int8>(); |
| uint32 index = LEB128(length); |
| switch (kind) |
| { |
| case ExternalKinds::Function: |
| { |
| FunctionIndexTypes::Type type = m_module->GetFunctionIndexType(index); |
| if (!FunctionIndexTypes::CanBeExported(type)) |
| { |
| ThrowDecodingError(_u("Invalid Export %u => func[%u]"), iExport, index); |
| } |
| m_module->SetExport(iExport, index, exportName, nameLength, kind); |
| |
| #if DBG_DUMP |
| if (type == FunctionIndexTypes::ImportThunk) |
| { |
| WasmImport* import = m_module->GetWasmFunctionInfo(index)->importedFunctionReference; |
| TRACE_WASM_DECODER(_u("Export #%u: Import(%s.%s)(%u) => %s"), iExport, import->modName, import->importName, index, exportName); |
| } |
| else |
| { |
| TRACE_WASM_DECODER(_u("Export #%u: Function(%u) => %s"), iExport, index, exportName); |
| } |
| #endif |
| break; |
| } |
| case ExternalKinds::Memory: |
| if (index != 0) |
| { |
| ThrowDecodingError(_u("Unknown memory index %u for export %s"), index, exportName); |
| } |
| m_module->SetExport(iExport, index, exportName, nameLength, kind); |
| break; |
| case ExternalKinds::Table: |
| if (index != 0) |
| { |
| ThrowDecodingError(_u("Unknown table index %u for export %s"), index, exportName); |
| } |
| m_module->SetExport(iExport, index, exportName, nameLength, kind); |
| break; |
| case ExternalKinds::Global: |
| if (index >= m_module->GetGlobalCount()) |
| { |
| ThrowDecodingError(_u("Unknown global %u for export %s"), index, exportName); |
| } |
| if (m_module->GetGlobal(index)->IsMutable()) |
| { |
| ThrowDecodingError(_u("Mutable globals cannot be exported"), index, exportName); |
| } |
| m_module->SetExport(iExport, index, exportName, nameLength, kind); |
| break; |
| default: |
| ThrowDecodingError(_u("Exported Kind %d, NYI"), kind); |
| break; |
| } |
| |
| } |
| } |
| |
| void WasmBinaryReader::ReadTableSection(bool isImportSection) |
| { |
| uint32 length; |
| uint32 entries; |
| if (isImportSection) |
| { |
| entries = 1; |
| } |
| else |
| { |
| entries = LEB128(length); |
| } |
| if (entries > 1) |
| { |
| ThrowDecodingError(_u("Maximum of one table allowed")); |
| } |
| |
| if (entries == 1) |
| { |
| int8 elementType = ReadConst<int8>(); |
| if (elementType != LanguageTypes::anyfunc) |
| { |
| ThrowDecodingError(_u("Only anyfunc type is supported. Unknown type %d"), elementType); |
| } |
| SectionLimits limits = ReadSectionLimits(Limits::GetMaxTableSize(), Limits::GetMaxTableSize(), _u("table too big")); |
| m_module->InitializeTable(limits.initial, limits.maximum); |
| TRACE_WASM_DECODER(_u("Indirect table: %u to %u entries"), limits.initial, limits.maximum); |
| } |
| } |
| |
| void WasmBinaryReader::ReadElementSection() |
| { |
| uint32 length = 0; |
| uint32 numSegments = LEB128(length); |
| if (numSegments > Limits::GetMaxElementSegments()) |
| { |
| ThrowDecodingError(_u("Too many element segments")); |
| } |
| |
| if (numSegments > 0) |
| { |
| m_module->AllocateElementSegs(numSegments); |
| } |
| TRACE_WASM_DECODER(_u("Indirect table element: %u entries"), numSegments); |
| |
| for (uint32 i = 0; i < numSegments; ++i) |
| { |
| uint32 index = LEB128(length); // Table id |
| if (index != 0 || !(m_module->HasTable() || m_module->HasTableImport())) |
| { |
| ThrowDecodingError(_u("Unknown table index %d"), index); //MVP limitation |
| } |
| |
| WasmNode initExpr = ReadInitExpr(true); |
| uint32 numElem = LEB128(length); |
| |
| if (numElem > Limits::GetMaxTableSize()) |
| { |
| ThrowDecodingError(_u("Too many table element")); |
| } |
| |
| WasmElementSegment* eSeg = Anew(m_alloc, WasmElementSegment, m_alloc, index, initExpr, numElem); |
| |
| for (uint32 iElem = 0; iElem < numElem; ++iElem) |
| { |
| uint32 elem = LEB128(length); |
| FunctionIndexTypes::Type funcType = m_module->GetFunctionIndexType(elem); |
| if (!FunctionIndexTypes::CanBeExported(funcType)) |
| { |
| ThrowDecodingError(_u("Invalid function to insert in the table %u"), elem); |
| } |
| eSeg->AddElement(elem); |
| } |
| m_module->SetElementSeg(eSeg, i); |
| } |
| } |
| |
| void WasmBinaryReader::ReadDataSection() |
| { |
| uint32 len = 0; |
| const uint32 numSegments = LEB128(len); |
| if (numSegments > Limits::GetMaxDataSegments()) |
| { |
| ThrowDecodingError(_u("Too many data segments")); |
| } |
| |
| if (numSegments > 0) |
| { |
| m_module->AllocateDataSegs(numSegments); |
| } |
| |
| for (uint32 i = 0; i < numSegments; ++i) |
| { |
| uint32 index = LEB128(len); |
| if (index != 0 || !(m_module->HasMemory() || m_module->HasMemoryImport())) |
| { |
| ThrowDecodingError(_u("Unknown memory index %u"), index); |
| } |
| TRACE_WASM_DECODER(_u("Data Segment #%u"), i); |
| WasmNode initExpr = ReadInitExpr(true); |
| uint32 dataByteLen = LEB128(len); |
| |
| WasmDataSegment* dseg = Anew(m_alloc, WasmDataSegment, m_alloc, initExpr, dataByteLen, m_pc); |
| CheckBytesLeft(dataByteLen); |
| m_pc += dataByteLen; |
| m_module->SetDataSeg(dseg, i); |
| } |
| } |
| |
| void WasmBinaryReader::ReadNameSection() |
| { |
| uint32 len = 0; |
| uint32 numFuncNames = LEB128(len); |
| |
| if (numFuncNames > Limits::GetMaxFunctions()) |
| { |
| ThrowDecodingError(_u("Too many function names")); |
| } |
| |
| for (uint32 i = 0; i < numFuncNames; ++i) |
| { |
| uint32 fnNameLen = 0; |
| WasmFunctionInfo* funsig = m_module->GetWasmFunctionInfo(i); |
| const char16* name = ReadInlineName(len, fnNameLen); |
| funsig->SetName(name, fnNameLen); |
| uint32 numLocals = LEB128(len); |
| if (numLocals != funsig->GetLocalCount()) |
| { |
| ThrowDecodingError(_u("num locals mismatch in names section")); |
| } |
| for (uint32 j = 0; j < numLocals; ++j) |
| { |
| uint32 localNameLen = 0; |
| ReadInlineName(len, localNameLen); |
| } |
| } |
| } |
| |
| void WasmBinaryReader::ReadGlobalSection() |
| { |
| uint32 len = 0; |
| uint32 numGlobals = LEB128(len); |
| |
| uint32 totalGlobals = 0; |
| if (UInt32Math::Add(numGlobals, m_module->GetGlobalCount(), &totalGlobals) || totalGlobals > Limits::GetMaxGlobals()) |
| { |
| ThrowDecodingError(_u("Too many globals")); |
| } |
| |
| for (uint32 i = 0; i < numGlobals; ++i) |
| { |
| WasmTypes::WasmType type = ReadWasmType(len); |
| bool isMutable = ReadMutableValue(); |
| WasmNode globalNode = ReadInitExpr(); |
| GlobalReferenceTypes::Type refType = GlobalReferenceTypes::Const; |
| WasmTypes::WasmType initType; |
| |
| switch (globalNode.op) { |
| case wbI32Const: initType = WasmTypes::I32; break; |
| case wbF32Const: initType = WasmTypes::F32; break; |
| case wbF64Const: initType = WasmTypes::F64; break; |
| case wbI64Const: initType = WasmTypes::I64; break; |
| case wbGetGlobal: |
| initType = m_module->GetGlobal(globalNode.var.num)->GetType(); |
| refType = GlobalReferenceTypes::LocalReference; |
| break; |
| default: |
| Assert(UNREACHED); |
| ThrowDecodingError(_u("Unknown global init_expr")); |
| } |
| if (type != initType) |
| { |
| ThrowDecodingError(_u("Type mismatch for global initialization")); |
| } |
| m_module->AddGlobal(refType, type, isMutable, globalNode); |
| } |
| } |
| |
| void WasmBinaryReader::ReadCustomSection() |
| { |
| CustomSection customSection; |
| customSection.name = m_currentSection.name; |
| customSection.nameLength = m_currentSection.nameLength; |
| customSection.payload = m_pc; |
| |
| size_t size = m_currentSection.end - m_pc; |
| if (m_currentSection.end < m_pc || !Math::FitsInDWord(size)) |
| { |
| ThrowDecodingError(_u("Invalid custom section size")); |
| } |
| customSection.payloadSize = (uint32)size; |
| m_module->AddCustomSection(customSection); |
| m_pc = m_currentSection.end; |
| } |
| |
| const char16* WasmBinaryReader::ReadInlineName(uint32& length, uint32& nameLength) |
| { |
| uint32 rawNameLength = LEB128(length); |
| if (rawNameLength > Limits::GetMaxStringSize()) |
| { |
| ThrowDecodingError(_u("Name too long")); |
| } |
| |
| CheckBytesLeft(rawNameLength); |
| LPCUTF8 rawName = m_pc; |
| |
| m_pc += rawNameLength; |
| length += rawNameLength; |
| |
| utf8::DecodeOptions decodeOptions = utf8::doDefault; |
| nameLength = (uint32)utf8::ByteIndexIntoCharacterIndex(rawName, rawNameLength, decodeOptions); |
| char16* contents = AnewArray(m_alloc, char16, nameLength + 1); |
| size_t decodedLength = utf8::DecodeUnitsIntoAndNullTerminate(contents, rawName, rawName + rawNameLength, decodeOptions); |
| if (decodedLength != nameLength) |
| { |
| AssertMsg(UNREACHED, "We calculated the length before decoding, what happened ?"); |
| ThrowDecodingError(_u("Error while decoding utf8 string")); |
| } |
| return contents; |
| } |
| |
| void WasmBinaryReader::ReadImportSection() |
| { |
| uint32 len = 0; |
| uint32 numImports = LEB128(len); |
| |
| if (numImports > Limits::GetMaxImports()) |
| { |
| ThrowDecodingError(_u("Too many imports")); |
| } |
| |
| for (uint32 i = 0; i < numImports; ++i) |
| { |
| uint32 modNameLen = 0, fnNameLen = 0; |
| const char16* modName = ReadInlineName(len, modNameLen); |
| const char16* fnName = ReadInlineName(len, fnNameLen); |
| |
| ExternalKinds::ExternalKind kind = (ExternalKinds::ExternalKind)ReadConst<int8>(); |
| TRACE_WASM_DECODER(_u("Import #%u: \"%s\".\"%s\", kind: %d"), i, modName, fnName, kind); |
| switch (kind) |
| { |
| case ExternalKinds::Function: |
| { |
| uint32 sigId = LEB128(len); |
| m_module->AddFunctionImport(sigId, modName, modNameLen, fnName, fnNameLen); |
| if (m_module->GetWasmFunctionCount() > Limits::GetMaxFunctions()) |
| { |
| ThrowDecodingError(_u("Too many functions")); |
| } |
| break; |
| } |
| case ExternalKinds::Global: |
| { |
| WasmTypes::WasmType type = ReadWasmType(len); |
| bool isMutable = ReadMutableValue(); |
| if (isMutable) |
| { |
| ThrowDecodingError(_u("Mutable globals cannot be imported")); |
| } |
| m_module->AddGlobal(GlobalReferenceTypes::ImportedReference, type, isMutable, {}); |
| m_module->AddGlobalImport(modName, modNameLen, fnName, fnNameLen); |
| if (m_module->GetGlobalCount() > Limits::GetMaxGlobals()) |
| { |
| ThrowDecodingError(_u("Too many globals")); |
| } |
| break; |
| } |
| case ExternalKinds::Table: |
| ReadTableSection(true); |
| m_module->AddTableImport(modName, modNameLen, fnName, fnNameLen); |
| break; |
| case ExternalKinds::Memory: |
| ReadMemorySection(true); |
| m_module->AddMemoryImport(modName, modNameLen, fnName, fnNameLen); |
| |
| break; |
| default: |
| ThrowDecodingError(_u("Imported Kind %d, NYI"), kind); |
| break; |
| } |
| } |
| } |
| |
| void WasmBinaryReader::ReadStartFunction() |
| { |
| uint32 len = 0; |
| uint32 id = LEB128(len); |
| Wasm::FunctionIndexTypes::Type funcType = m_module->GetFunctionIndexType(id); |
| if (!FunctionIndexTypes::CanBeExported(funcType)) |
| { |
| ThrowDecodingError(_u("Invalid function index for start function %u"), id); |
| } |
| WasmSignature* sig = m_module->GetWasmFunctionInfo(id)->GetSignature(); |
| if (sig->GetParamCount() > 0 || sig->GetResultType() != WasmTypes::Void) |
| { |
| ThrowDecodingError(_u("Start function must be void and nullary")); |
| } |
| m_module->SetStartFunction(id); |
| } |
| |
| template<typename MaxAllowedType> |
| MaxAllowedType WasmBinaryReader::LEB128(uint32 &length, bool sgn) |
| { |
| MaxAllowedType result = 0; |
| uint32 shamt = 0; |
| byte b = 0; |
| length = 1; |
| uint32 maxReads = sizeof(MaxAllowedType) == 4 ? 5 : 10; |
| CompileAssert(sizeof(MaxAllowedType) == 4 || sizeof(MaxAllowedType) == 8); |
| |
| for (uint32 i = 0; i < maxReads; i++, length++) |
| { |
| CheckBytesLeft(1); |
| b = *m_pc++; |
| result = result | ((MaxAllowedType)(b & 0x7f) << shamt); |
| if (sgn) |
| { |
| shamt += 7; |
| if ((b & 0x80) == 0) |
| break; |
| } |
| else |
| { |
| if ((b & 0x80) == 0) |
| break; |
| shamt += 7; |
| } |
| } |
| |
| if (b & 0x80 || m_pc > m_end) |
| { |
| ThrowDecodingError(_u("Invalid LEB128 format")); |
| } |
| |
| if (sgn && (shamt < sizeof(MaxAllowedType) * 8) && (0x40 & b)) |
| { |
| if (sizeof(MaxAllowedType) == 4) |
| { |
| result |= -(1 << shamt); |
| } |
| else if (sizeof(MaxAllowedType) == 8) |
| { |
| result |= -((int64)1 << shamt); |
| } |
| } |
| |
| if (!sgn) |
| { |
| if (sizeof(MaxAllowedType) == 4) |
| { |
| TRACE_WASM_LEB128(_u("Binary decoder: LEB128 length = %u, value = %u (0x%x)"), length, result, result); |
| } |
| else if (sizeof(MaxAllowedType) == 8) |
| { |
| TRACE_WASM_LEB128(_u("Binary decoder: LEB128 length = %u, value = %llu (0x%llx)"), length, result, result); |
| } |
| } |
| |
| return result; |
| } |
| |
| // Signed LEB128 |
| template<> |
| int32 WasmBinaryReader::SLEB128(uint32 &length) |
| { |
| int32 result = LEB128<uint32>(length, true); |
| |
| TRACE_WASM_LEB128(_u("Binary decoder: SLEB128 length = %u, value = %d (0x%x)"), length, result, result); |
| return result; |
| } |
| |
| template<> |
| int64 WasmBinaryReader::SLEB128(uint32 &length) |
| { |
| int64 result = LEB128<uint64>(length, true); |
| |
| TRACE_WASM_LEB128(_u("Binary decoder: SLEB128 length = %u, value = %lld (0x%llx)"), length, result, result); |
| return result; |
| } |
| |
| WasmNode WasmBinaryReader::ReadInitExpr(bool isOffset) |
| { |
| if (m_readerState != READER_STATE_MODULE) |
| { |
| ThrowDecodingError(_u("Wasm reader in an invalid state to read init_expr")); |
| } |
| |
| m_funcState.count = 0; |
| m_funcState.size = m_currentSection.end - m_pc; |
| ReadExpr(); |
| WasmNode node = m_currentNode; |
| switch (node.op) |
| { |
| case wbI32Const: |
| case wbF32Const: |
| case wbI64Const: |
| case wbF64Const: |
| break; |
| case wbGetGlobal: |
| { |
| uint32 globalIndex = node.var.num; |
| WasmGlobal* global = m_module->GetGlobal(globalIndex); |
| if (global->GetReferenceType() != GlobalReferenceTypes::ImportedReference) |
| { |
| ThrowDecodingError(_u("initializer expression can only use imported globals")); |
| } |
| if (global->IsMutable()) |
| { |
| ThrowDecodingError(_u("initializer expression cannot reference a mutable global")); |
| } |
| break; |
| } |
| default: |
| ThrowDecodingError(_u("Invalid initexpr opcode")); |
| } |
| |
| if (ReadExpr() != wbEnd) |
| { |
| ThrowDecodingError(_u("Missing end opcode after init expr")); |
| } |
| if (isOffset) |
| { |
| m_module->ValidateInitExportForOffset(node); |
| } |
| return node; |
| } |
| |
| SectionLimits WasmBinaryReader::ReadSectionLimits(uint32 maxInitial, uint32 maxMaximum, const char16* errorMsg) |
| { |
| SectionLimits limits; |
| uint32 length = 0; |
| uint32 flags = LEB128(length); |
| limits.initial = LEB128(length); |
| limits.maximum = maxMaximum; |
| if (flags & 0x1) |
| { |
| limits.maximum = LEB128(length); |
| if (limits.maximum > maxMaximum) |
| { |
| ThrowDecodingError(_u("Maximum %s"), errorMsg); |
| } |
| } |
| if (limits.initial > maxInitial) |
| { |
| ThrowDecodingError(_u("Minimum %s"), errorMsg); |
| } |
| return limits; |
| } |
| |
| template <typename T> |
| T WasmBinaryReader::ReadConst() |
| { |
| CheckBytesLeft(sizeof(T)); |
| T value = *((T*)m_pc); |
| m_pc += sizeof(T); |
| |
| return value; |
| } |
| |
| uint8 WasmBinaryReader::ReadVarUInt7() |
| { |
| return ReadConst<uint8>() & 0x7F; |
| } |
| |
| bool WasmBinaryReader::ReadMutableValue() |
| { |
| uint8 mutableValue = ReadConst<UINT8>(); |
| switch (mutableValue) |
| { |
| case 0: return false; |
| case 1: return true; |
| default: |
| ThrowDecodingError(_u("invalid mutability")); |
| } |
| } |
| |
| WasmTypes::WasmType WasmBinaryReader::ReadWasmType(uint32& length) |
| { |
| length = 1; |
| return LanguageTypes::ToWasmType(ReadConst<int8>()); |
| } |
| |
| void WasmBinaryReader::CheckBytesLeft(uint32 bytesNeeded) |
| { |
| uint32 bytesLeft = (uint32)(m_end - m_pc); |
| if (bytesNeeded > bytesLeft) |
| { |
| ThrowDecodingError(_u("Out of file: Needed: %d, Left: %d"), bytesNeeded, bytesLeft); |
| } |
| } |
| |
| } // namespace Wasm |
| |
| #endif // ENABLE_WASM |