| /* | |
| * Copyright 2016 WebAssembly Community Group participants | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include <algorithm> | |
| #include <fstream> | |
| #include <optional> | |
| #include "ir/memory-utils.h" | |
| #include "ir/module-utils.h" | |
| #include "ir/names.h" | |
| #include "ir/table-utils.h" | |
| #include "ir/type-updating.h" | |
| #include "pass.h" | |
| #include "support/bits.h" | |
| #include "support/stdckdint.h" | |
| #include "support/string.h" | |
| #include "support/utilities.h" | |
| #include "wasm-annotations.h" | |
| #include "wasm-binary.h" | |
| #include "wasm-debug.h" | |
| #include "wasm-limits.h" | |
| #include "wasm-stack.h" | |
| #define DEBUG_TYPE "binary" | |
| namespace wasm { | |
| void WasmBinaryWriter::prepare() { | |
| // Collect function types and their frequencies. Collect information in each | |
| // function in parallel, then merge. | |
| indexedTypes = ModuleUtils::getOptimizedIndexedHeapTypes(*wasm); | |
| for (Index i = 0, size = indexedTypes.types.size(); i < size; ++i) { | |
| if (indexedTypes.types[i].isSignature()) { | |
| signatureIndexes.insert({indexedTypes.types[i].getSignature(), i}); | |
| } | |
| } | |
| importInfo = std::make_unique<ImportInfo>(*wasm); | |
| } | |
| void WasmBinaryWriter::write() { | |
| writeHeader(); | |
| writeDylinkSection(); | |
| initializeDebugInfo(); | |
| if (sourceMap) { | |
| writeSourceMapProlog(); | |
| } | |
| writeTypes(); | |
| writeImports(); | |
| writeFunctionSignatures(); | |
| writeTableDeclarations(); | |
| writeMemories(); | |
| writeTags(); | |
| if (wasm->features.hasStrings()) { | |
| writeStrings(); | |
| } | |
| writeGlobals(); | |
| writeExports(); | |
| writeStart(); | |
| writeElementSegments(); | |
| writeDataCount(); | |
| writeFunctions(); | |
| writeDataSegments(); | |
| if (debugInfo || emitModuleName) { | |
| writeNames(); | |
| } | |
| if (sourceMap && !sourceMapUrl.empty()) { | |
| writeSourceMapUrl(); | |
| } | |
| if (symbolMap.size() > 0) { | |
| writeSymbolMap(); | |
| } | |
| if (sourceMap) { | |
| writeSourceMapEpilog(); | |
| } | |
| #ifdef BUILD_LLVM_DWARF | |
| // Update DWARF user sections after writing the data they refer to | |
| // (function bodies), and before writing the user sections themselves. | |
| if (Debug::hasDWARFSections(*wasm)) { | |
| Debug::writeDWARFSections(*wasm, binaryLocations); | |
| } | |
| #endif | |
| writeLateCustomSections(); | |
| writeFeaturesSection(); | |
| } | |
| void WasmBinaryWriter::writeHeader() { | |
| o << int32_t(BinaryConsts::Magic); // magic number \0asm | |
| o << int32_t(BinaryConsts::Version); | |
| } | |
| int32_t WasmBinaryWriter::writeU32LEBPlaceholder() { | |
| return o.writeU32LEBPlaceholder(); | |
| } | |
| void WasmBinaryWriter::writeResizableLimits( | |
| Address initial, | |
| Address maximum, | |
| bool hasMaximum, | |
| bool shared, | |
| bool is64, | |
| std::optional<uint8_t> pageSizeLog2) { | |
| uint8_t actualPageSizeLog2 = | |
| pageSizeLog2 ? *pageSizeLog2 : Memory::kDefaultPageSizeLog2; | |
| uint32_t flags = (hasMaximum ? (uint32_t)BinaryConsts::HasMaximum : 0U) | | |
| (shared ? (uint32_t)BinaryConsts::IsShared : 0U) | | |
| (is64 ? (uint32_t)BinaryConsts::Is64 : 0U) | | |
| (actualPageSizeLog2 != Memory::kDefaultPageSizeLog2 | |
| ? (uint32_t)BinaryConsts::HasCustomPageSize | |
| : 0U); | |
| o << U32LEB(flags); | |
| if (is64) { | |
| o << U64LEB(initial); | |
| if (hasMaximum) { | |
| o << U64LEB(maximum); | |
| } | |
| } else { | |
| o << U32LEB(initial); | |
| if (hasMaximum) { | |
| o << U32LEB(maximum); | |
| } | |
| } | |
| if (actualPageSizeLog2 != Memory::kDefaultPageSizeLog2) { | |
| o << U32LEB(actualPageSizeLog2); | |
| } | |
| } | |
| template<typename T> int32_t WasmBinaryWriter::startSection(T code) { | |
| o << uint8_t(code); | |
| if (sourceMap) { | |
| sourceMapLocationsSizeAtSectionStart = sourceMapLocations.size(); | |
| } | |
| binaryLocationsSizeAtSectionStart = binaryLocations.expressions.size(); | |
| return writeU32LEBPlaceholder(); // section size to be filled in later | |
| } | |
| void WasmBinaryWriter::finishSection(int32_t start) { | |
| auto adjustmentForLEBShrinking = o.emitRetroactiveSectionSizeLEB(start); | |
| if (adjustmentForLEBShrinking && sourceMap) { | |
| for (auto i = sourceMapLocationsSizeAtSectionStart; | |
| i < sourceMapLocations.size(); | |
| ++i) { | |
| sourceMapLocations[i].first -= adjustmentForLEBShrinking; | |
| } | |
| } | |
| if (binaryLocationsSizeAtSectionStart != binaryLocations.expressions.size()) { | |
| // We added the binary locations, adjust them: they must be relative | |
| // to the code section. | |
| assert(binaryLocationsSizeAtSectionStart == 0); | |
| // The section type byte is right before the LEB for the size; we want | |
| // offsets that are relative to the body, which is after that section type | |
| // byte and the the size LEB. | |
| // | |
| // We can compute the size of the size field LEB by considering the original | |
| // size of the maximal LEB, and the adjustment due to shrinking. | |
| auto sizeFieldSize = MaxLEB32Bytes - adjustmentForLEBShrinking; | |
| auto body = start + sizeFieldSize; | |
| // Offsets are relative to the body of the code section: after the | |
| // section type byte and the size. | |
| // Everything was moved by the adjustment, track that. After this, | |
| // we are at the right absolute address. | |
| // We are relative to the section start. | |
| auto totalAdjustment = adjustmentForLEBShrinking + body; | |
| for (auto& [_, locations] : binaryLocations.expressions) { | |
| locations.start -= totalAdjustment; | |
| locations.end -= totalAdjustment; | |
| } | |
| for (auto& [_, locations] : binaryLocations.functions) { | |
| locations.start -= totalAdjustment; | |
| locations.declarations -= totalAdjustment; | |
| locations.end -= totalAdjustment; | |
| } | |
| for (auto& [_, locations] : binaryLocations.delimiters) { | |
| for (auto& item : locations) { | |
| item -= totalAdjustment; | |
| } | |
| } | |
| } | |
| } | |
| int32_t WasmBinaryWriter::startSubsection( | |
| BinaryConsts::CustomSections::Subsection code) { | |
| return startSection(code); | |
| } | |
| void WasmBinaryWriter::finishSubsection(int32_t start) { finishSection(start); } | |
| void WasmBinaryWriter::writeStart() { | |
| if (!wasm->start.is()) { | |
| return; | |
| } | |
| auto start = startSection(BinaryConsts::Section::Start); | |
| o << U32LEB(getFunctionIndex(wasm->start.view())); | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeMemories() { | |
| if (importInfo->getNumDefinedMemories() == 0) { | |
| return; | |
| } | |
| auto start = startSection(BinaryConsts::Section::Memory); | |
| auto num = importInfo->getNumDefinedMemories(); | |
| o << U32LEB(num); | |
| ModuleUtils::iterDefinedMemories(*wasm, [&](Memory* memory) { | |
| writeResizableLimits(memory->initial, | |
| memory->max, | |
| memory->hasMax(), | |
| memory->shared, | |
| memory->is64(), | |
| memory->pageSizeLog2); | |
| }); | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeTypes() { | |
| if (indexedTypes.types.size() == 0) { | |
| return; | |
| } | |
| // Count the number of recursion groups, which is the number of elements in | |
| // the type section. | |
| size_t numGroups = 0; | |
| { | |
| std::optional<RecGroup> lastGroup; | |
| for (auto type : indexedTypes.types) { | |
| auto currGroup = type.getRecGroup(); | |
| numGroups += lastGroup != currGroup; | |
| lastGroup = currGroup; | |
| } | |
| } | |
| // As a temporary measure, detect which types have subtypes and always use | |
| // `sub` or `sub final` for these types. The standard says that types without | |
| // `sub` or `sub final` are final, but we currently treat them as non-final. | |
| // To avoid unsafe ambiguity, only use the short form for types that it would | |
| // be safe to treat as final, i.e. types without subtypes. | |
| std::vector<bool> hasSubtypes(indexedTypes.types.size()); | |
| for (auto type : indexedTypes.types) { | |
| if (auto super = type.getDeclaredSuperType()) { | |
| hasSubtypes[indexedTypes.indices[*super]] = true; | |
| } | |
| } | |
| auto start = startSection(BinaryConsts::Section::Type); | |
| o << U32LEB(numGroups); | |
| std::optional<RecGroup> lastGroup = std::nullopt; | |
| for (Index i = 0; i < indexedTypes.types.size(); ++i) { | |
| auto type = indexedTypes.types[i]; | |
| // Check whether we need to start a new recursion group. Recursion groups of | |
| // size 1 are implicit, so only emit a group header for larger groups. | |
| auto currGroup = type.getRecGroup(); | |
| if (lastGroup != currGroup && currGroup.size() > 1) { | |
| o << uint8_t(BinaryConsts::EncodedType::Rec) << U32LEB(currGroup.size()); | |
| } | |
| lastGroup = currGroup; | |
| // Emit the type definition. | |
| auto super = type.getDeclaredSuperType(); | |
| if (super || type.isOpen()) { | |
| if (type.isOpen()) { | |
| o << uint8_t(BinaryConsts::EncodedType::Sub); | |
| } else { | |
| o << uint8_t(BinaryConsts::EncodedType::SubFinal); | |
| } | |
| if (super) { | |
| o << U32LEB(1); | |
| writeHeapType(*super, Inexact); | |
| } else { | |
| o << U32LEB(0); | |
| } | |
| } | |
| if (type.isShared()) { | |
| o << uint8_t(BinaryConsts::EncodedType::Shared); | |
| } | |
| if (auto desc = type.getDescribedType()) { | |
| o << uint8_t(BinaryConsts::EncodedType::Describes); | |
| writeHeapType(*desc, Inexact); | |
| } | |
| if (auto desc = type.getDescriptorType()) { | |
| o << uint8_t(BinaryConsts::EncodedType::Descriptor); | |
| writeHeapType(*desc, Inexact); | |
| } | |
| switch (type.getKind()) { | |
| case HeapTypeKind::Func: { | |
| o << uint8_t(BinaryConsts::EncodedType::Func); | |
| auto sig = type.getSignature(); | |
| for (auto& sigType : {sig.params, sig.results}) { | |
| o << U32LEB(sigType.size()); | |
| for (const auto& type : sigType) { | |
| writeType(type); | |
| } | |
| } | |
| break; | |
| } | |
| case HeapTypeKind::Struct: { | |
| o << uint8_t(BinaryConsts::EncodedType::Struct); | |
| auto fields = type.getStruct().fields; | |
| o << U32LEB(fields.size()); | |
| for (const auto& field : fields) { | |
| writeField(field); | |
| } | |
| break; | |
| } | |
| case HeapTypeKind::Array: | |
| o << uint8_t(BinaryConsts::EncodedType::Array); | |
| writeField(type.getArray().element); | |
| break; | |
| case HeapTypeKind::Cont: | |
| o << uint8_t(BinaryConsts::EncodedType::Cont); | |
| writeHeapType(type.getContinuation().type, Inexact); | |
| break; | |
| case HeapTypeKind::Basic: | |
| WASM_UNREACHABLE("unexpected kind"); | |
| } | |
| } | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeImports() { | |
| auto num = importInfo->getNumImports(); | |
| if (num == 0) { | |
| return; | |
| } | |
| auto start = startSection(BinaryConsts::Section::Import); | |
| using ImportItem = std::variant<Function*, Global*, Tag*, Memory*, Table*>; | |
| std::vector<ImportItem> imports; | |
| imports.reserve(num); | |
| ModuleUtils::iterImports(*wasm, | |
| [&](ImportItem item) { imports.push_back(item); }); | |
| auto getModule = [](const ImportItem& item) -> Name { | |
| return std::visit([](auto* i) { return i->module; }, item); | |
| }; | |
| auto getBase = [](const ImportItem& item) -> Name { | |
| return std::visit([](auto* i) { return i->base; }, item); | |
| }; | |
| auto shareImportType = [&](const ImportItem& a, const ImportItem& b) -> bool { | |
| return std::visit( | |
| overloaded{ | |
| [](Function* a, Function* b) { return a->type == b->type; }, | |
| [](Global* a, Global* b) { | |
| return a->type == b->type && a->mutable_ == b->mutable_; | |
| }, | |
| [](Tag* a, Tag* b) { return a->type == b->type; }, | |
| [](Memory* a, Memory* b) { return MemoryUtils::sameType(*a, *b); }, | |
| [](Table* a, Table* b) { return TableUtils::sameType(*a, *b); }, | |
| [](const auto& a, const auto& b) { return false; }}, | |
| a, | |
| b); | |
| }; | |
| struct ImportGroup { | |
| enum Kind { Single, SharedAll, SharedModule } kind; | |
| size_t start; | |
| size_t count; | |
| }; | |
| std::vector<ImportGroup> groups; | |
| if (wasm->features.hasCompactImports()) { | |
| size_t i = 0; | |
| size_t numImports = imports.size(); | |
| while (i < numImports) { | |
| // If the next import shares the module and type, then greedily collect | |
| // the following imports as long as they share both the module and type. | |
| size_t run = 1; | |
| while (i + run < numImports && | |
| getModule(imports[i]) == getModule(imports[i + run]) && | |
| shareImportType(imports[i], imports[i + run])) { | |
| ++run; | |
| } | |
| if (run > 1) { | |
| groups.push_back({ImportGroup::SharedAll, i, run}); | |
| i += run; | |
| continue; | |
| } | |
| // Otherwise, try greedily collecting imports that share just the module. | |
| while (i + run < numImports && | |
| getModule(imports[i]) == getModule(imports[i + run])) { | |
| ++run; | |
| } | |
| if (run > 1) { | |
| groups.push_back({ImportGroup::SharedModule, i, run}); | |
| i += run; | |
| continue; | |
| } | |
| // Otherwise, just use a normal import. | |
| groups.push_back({ImportGroup::Single, i, 1}); | |
| ++i; | |
| } | |
| } else { | |
| for (size_t i = 0; i < imports.size(); ++i) { | |
| groups.push_back({ImportGroup::Single, i, 1}); | |
| } | |
| } | |
| o << U32LEB(groups.size()); | |
| auto writeImportDesc = [&](const ImportItem& item) { | |
| std::visit(overloaded{[&](Function* func) { | |
| uint32_t kind = ExternalKind::Function; | |
| if (func->type.isExact()) { | |
| kind |= BinaryConsts::ExactImport; | |
| } | |
| o << U32LEB(kind) | |
| << U32LEB(getTypeIndex(func->type.getHeapType())); | |
| }, | |
| [&](Global* global) { | |
| o << U32LEB(int32_t(ExternalKind::Global)); | |
| writeType(global->type); | |
| o << U32LEB(global->mutable_); | |
| }, | |
| [&](Tag* tag) { | |
| o << U32LEB(int32_t(ExternalKind::Tag)); | |
| // Reserved 'attribute' field. Always 0. | |
| o << uint8_t(0); | |
| o << U32LEB(getTypeIndex(tag->type)); | |
| }, | |
| [&](Memory* memory) { | |
| o << U32LEB(int32_t(ExternalKind::Memory)); | |
| writeResizableLimits(memory->initial, | |
| memory->max, | |
| memory->hasMax(), | |
| memory->shared, | |
| memory->is64(), | |
| memory->pageSizeLog2); | |
| }, | |
| [&](Table* table) { | |
| o << U32LEB(int32_t(ExternalKind::Table)); | |
| writeType(table->type); | |
| writeResizableLimits(table->initial, | |
| table->max, | |
| table->hasMax(), | |
| /*shared=*/false, | |
| table->is64()); | |
| }}, | |
| item); | |
| }; | |
| for (const auto& group : groups) { | |
| switch (group.kind) { | |
| case ImportGroup::Single: { | |
| const auto& item = imports[group.start]; | |
| writeInlineString(getModule(item).view()); | |
| writeInlineString(getBase(item).view()); | |
| writeImportDesc(item); | |
| break; | |
| } | |
| case ImportGroup::SharedAll: { | |
| const auto& first = imports[group.start]; | |
| writeInlineString(getModule(first).view()); | |
| writeInlineString(""); | |
| o << uint8_t(BinaryConsts::CompactImportsSharedAll); | |
| writeImportDesc(first); | |
| o << U32LEB(group.count); | |
| for (size_t i = 0; i < group.count; ++i) { | |
| writeInlineString(getBase(imports[group.start + i]).view()); | |
| } | |
| break; | |
| } | |
| case ImportGroup::SharedModule: { | |
| const auto& first = imports[group.start]; | |
| writeInlineString(getModule(first).view()); | |
| writeInlineString(""); | |
| o << uint8_t(BinaryConsts::CompactImportsSharedModule); | |
| o << U32LEB(group.count); | |
| for (size_t i = 0; i < group.count; ++i) { | |
| const auto& item = imports[group.start + i]; | |
| writeInlineString(getBase(item).view()); | |
| writeImportDesc(item); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeFunctionSignatures() { | |
| if (importInfo->getNumDefinedFunctions() == 0) { | |
| return; | |
| } | |
| auto start = startSection(BinaryConsts::Section::Function); | |
| o << U32LEB(importInfo->getNumDefinedFunctions()); | |
| ModuleUtils::iterDefinedFunctions(*wasm, [&](Function* func) { | |
| o << U32LEB(getTypeIndex(func->type.getHeapType())); | |
| }); | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeExpression(Expression* curr) { | |
| BinaryenIRToBinaryWriter(*this, o).visit(curr); | |
| } | |
| void WasmBinaryWriter::noteLabelNames( | |
| Function* func, std::vector<std::pair<Index, Name>>& labelNames) { | |
| if (!labelNames.empty()) { | |
| funcLabelNames[func->name] = std::move(labelNames); | |
| } | |
| } | |
| void WasmBinaryWriter::writeFunctions() { | |
| if (importInfo->getNumDefinedFunctions() == 0) { | |
| return; | |
| } | |
| std::optional<ModuleStackIR> moduleStackIR; | |
| if (options.generateStackIR) { | |
| moduleStackIR.emplace(*wasm, options); | |
| } | |
| auto sectionStart = startSection(BinaryConsts::Section::Code); | |
| o << U32LEB(importInfo->getNumDefinedFunctions()); | |
| bool DWARF = Debug::hasDWARFSections(*getModule()); | |
| ModuleUtils::iterDefinedFunctions(*wasm, [&](Function* func) { | |
| assert(binaryLocationTrackedExpressionsForFunc.empty()); | |
| // Do not smear any debug location from the previous function. | |
| writeNoDebugLocation(); | |
| size_t sourceMapLocationsSizeAtFunctionStart = sourceMapLocations.size(); | |
| size_t sizePos = writeU32LEBPlaceholder(); | |
| size_t start = o.size(); | |
| // Emit Stack IR if present. | |
| StackIR* stackIR = nullptr; | |
| if (moduleStackIR) { | |
| stackIR = moduleStackIR->getStackIROrNull(func); | |
| } | |
| if (stackIR) { | |
| StackIRToBinaryWriter writer(*this, o, func, *stackIR, sourceMap, DWARF); | |
| writer.write(); | |
| if (debugInfo) { | |
| funcMappedLocals[func->name] = std::move(writer.getMappedLocals()); | |
| noteLabelNames(func, writer.getLabelNames()); | |
| } | |
| } else { | |
| BinaryenIRToBinaryWriter writer(*this, o, func, sourceMap, DWARF); | |
| writer.write(); | |
| if (debugInfo) { | |
| funcMappedLocals[func->name] = std::move(writer.getMappedLocals()); | |
| noteLabelNames(func, writer.getLabelNames()); | |
| } | |
| } | |
| size_t size = o.size() - start; | |
| assert(size <= std::numeric_limits<uint32_t>::max()); | |
| auto sizeFieldSize = o.writeAt(sizePos, U32LEB(size)); | |
| // We can move things back if the actual LEB for the size doesn't use the | |
| // maximum 5 bytes. In that case we need to adjust offsets after we move | |
| // things backwards. | |
| auto adjustmentForLEBShrinking = MaxLEB32Bytes - sizeFieldSize; | |
| if (adjustmentForLEBShrinking) { | |
| // we can save some room, nice | |
| assert(sizeFieldSize < MaxLEB32Bytes); | |
| std::move(&o[start], &o[start] + size, &o[sizePos] + sizeFieldSize); | |
| o.resize(o.size() - adjustmentForLEBShrinking); | |
| if (sourceMap) { | |
| for (auto i = sourceMapLocationsSizeAtFunctionStart; | |
| i < sourceMapLocations.size(); | |
| ++i) { | |
| sourceMapLocations[i].first -= adjustmentForLEBShrinking; | |
| } | |
| } | |
| for (auto* curr : binaryLocationTrackedExpressionsForFunc) { | |
| // We added the binary locations, adjust them: they must be relative | |
| // to the code section. | |
| auto& span = binaryLocations.expressions[curr]; | |
| span.start -= adjustmentForLEBShrinking; | |
| span.end -= adjustmentForLEBShrinking; | |
| auto iter = binaryLocations.delimiters.find(curr); | |
| if (iter != binaryLocations.delimiters.end()) { | |
| for (auto& item : iter->second) { | |
| item -= adjustmentForLEBShrinking; | |
| } | |
| } | |
| } | |
| } | |
| // We need to track the function location if we are tracking the locations | |
| // of expressions inside it, or, if it has code annotations (the function | |
| // itself may be annotated, even if nothing inside it is). | |
| if (!binaryLocationTrackedExpressionsForFunc.empty() || | |
| !func->codeAnnotations.empty()) { | |
| binaryLocations.functions[func] = BinaryLocations::FunctionLocations{ | |
| BinaryLocation(sizePos), | |
| BinaryLocation(start - adjustmentForLEBShrinking), | |
| BinaryLocation(o.size())}; | |
| } | |
| tableOfContents.functionBodies.emplace_back( | |
| func->name, sizePos + sizeFieldSize, size); | |
| binaryLocationTrackedExpressionsForFunc.clear(); | |
| if (func->getParams().size() > WebLimitations::MaxFunctionParams) { | |
| std::cerr << "Some VMs may not accept this binary because it has a large " | |
| << "number of parameters in function " << func->name << ".\n"; | |
| } | |
| if (func->getNumLocals() > WebLimitations::MaxFunctionLocals) { | |
| std::cerr << "Some VMs may not accept this binary because it has a large " | |
| << "number of locals in function " << func->name << ".\n"; | |
| } | |
| }); | |
| finishSection(sectionStart); | |
| // Code annotations must come before the code section (see comment on | |
| // writeCodeAnnotations). | |
| if (auto annotations = writeCodeAnnotations()) { | |
| // We need to move the code section and put the annotations before it. | |
| auto& annotationsBuffer = *annotations; | |
| auto oldSize = o.size(); | |
| auto annotationsSectionSize = annotationsBuffer.size(); | |
| o.resize(oldSize + annotationsSectionSize); | |
| // |sectionStart| is the start of the contents of the section. Subtract 1 to | |
| // include the section code as well, so we move all of it. | |
| std::move_backward(&o[sectionStart - 1], &o[oldSize], o.end()); | |
| std::copy( | |
| annotationsBuffer.begin(), annotationsBuffer.end(), &o[sectionStart - 1]); | |
| // Source map offsets are absolute (from the start of the binary) so we must | |
| // adjust them after moving the code section. | |
| if (sourceMap) { | |
| for (auto& location : sourceMapLocations) { | |
| location.first += annotationsSectionSize; | |
| } | |
| } | |
| } | |
| } | |
| void WasmBinaryWriter::writeStrings() { | |
| assert(wasm->features.hasStrings()); | |
| // Scan the entire wasm to find the relevant strings. | |
| // To find all the string literals we must scan all the code. | |
| using StringSet = std::unordered_set<Name>; | |
| struct StringWalker : public PostWalker<StringWalker> { | |
| StringSet& strings; | |
| StringWalker(StringSet& strings) : strings(strings) {} | |
| void visitStringConst(StringConst* curr) { strings.insert(curr->string); } | |
| }; | |
| ModuleUtils::ParallelFunctionAnalysis<StringSet> analysis( | |
| *wasm, [&](Function* func, StringSet& strings) { | |
| if (!func->imported()) { | |
| StringWalker(strings).walk(func->body); | |
| } | |
| }); | |
| // Also walk the global module code (for simplicity, also add it to the | |
| // function map, using a "function" key of nullptr). | |
| auto& globalStrings = analysis.map[nullptr]; | |
| StringWalker(globalStrings).walkModuleCode(wasm); | |
| // Generate the indexes from the combined set of necessary strings, | |
| // which we sort for determinism. | |
| StringSet allStrings; | |
| for (auto& [func, strings] : analysis.map) { | |
| for (auto& string : strings) { | |
| allStrings.insert(string); | |
| } | |
| } | |
| std::vector<Name> sorted; | |
| for (auto& string : allStrings) { | |
| sorted.push_back(string); | |
| } | |
| std::sort(sorted.begin(), sorted.end()); | |
| for (Index i = 0; i < sorted.size(); i++) { | |
| stringIndexes[sorted[i]] = i; | |
| } | |
| auto num = sorted.size(); | |
| if (num == 0) { | |
| return; | |
| } | |
| auto start = startSection(BinaryConsts::Section::Strings); | |
| // Placeholder for future use in the spec. | |
| o << U32LEB(0); | |
| // The number of strings and then their contents. | |
| o << U32LEB(num); | |
| for (auto& string : sorted) { | |
| // Re-encode from WTF-16 to WTF-8. | |
| std::stringstream wtf8; | |
| [[maybe_unused]] bool valid = | |
| String::convertWTF16ToWTF8(wtf8, string.view()); | |
| assert(valid); | |
| // TODO: Use wtf8.view() once we have C++20. | |
| writeInlineString(wtf8.str()); | |
| } | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeGlobals() { | |
| if (importInfo->getNumDefinedGlobals() == 0) { | |
| return; | |
| } | |
| auto start = startSection(BinaryConsts::Section::Global); | |
| // Count and emit the total number of globals after tuple globals have been | |
| // expanded into their constituent parts. | |
| Index num = 0; | |
| ModuleUtils::iterDefinedGlobals( | |
| *wasm, [&num](Global* global) { num += global->type.size(); }); | |
| o << U32LEB(num); | |
| ModuleUtils::iterDefinedGlobals(*wasm, [&](Global* global) { | |
| size_t i = 0; | |
| for (const auto& t : global->type) { | |
| writeType(t); | |
| o << U32LEB(global->mutable_); | |
| if (global->type.size() == 1) { | |
| writeExpression(global->init); | |
| } else if (auto* make = global->init->dynCast<TupleMake>()) { | |
| // Emit the proper lane for this global. | |
| writeExpression(make->operands[i]); | |
| } else { | |
| // For now tuple globals must contain tuple.make. We could perhaps | |
| // support more operations, like global.get, but the code would need to | |
| // look something like this: | |
| // | |
| // auto parentIndex = getGlobalIndex(get->name); | |
| // o << int8_t(BinaryConsts::GlobalGet) << U32LEB(parentIndex + i); | |
| // | |
| // That is, we must emit the instruction here, and not defer to | |
| // writeExpression, as writeExpression writes an entire expression at a | |
| // time (and not just one of the lanes). As emitting an instruction here | |
| // is less clean, and there is no important use case for global.get of | |
| // one tuple global to another, we disallow this. | |
| WASM_UNREACHABLE("unsupported tuple global operation"); | |
| } | |
| o << int8_t(BinaryConsts::End); | |
| ++i; | |
| } | |
| }); | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeExports() { | |
| if (wasm->exports.size() == 0) { | |
| return; | |
| } | |
| auto start = startSection(BinaryConsts::Section::Export); | |
| o << U32LEB(wasm->exports.size()); | |
| for (auto& curr : wasm->exports) { | |
| writeInlineString(curr->name.view()); | |
| o << U32LEB(int32_t(curr->kind)); | |
| switch (curr->kind) { | |
| case ExternalKind::Function: | |
| o << U32LEB(getFunctionIndex(*curr->getInternalName())); | |
| break; | |
| case ExternalKind::Table: | |
| o << U32LEB(getTableIndex(*curr->getInternalName())); | |
| break; | |
| case ExternalKind::Memory: | |
| o << U32LEB(getMemoryIndex(*curr->getInternalName())); | |
| break; | |
| case ExternalKind::Global: | |
| o << U32LEB(getGlobalIndex(*curr->getInternalName())); | |
| break; | |
| case ExternalKind::Tag: | |
| o << U32LEB(getTagIndex(*curr->getInternalName())); | |
| break; | |
| default: | |
| WASM_UNREACHABLE("unexpected extern kind"); | |
| } | |
| } | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeDataCount() { | |
| if (!wasm->features.hasBulkMemory() || !wasm->dataSegments.size()) { | |
| return; | |
| } | |
| auto start = startSection(BinaryConsts::Section::DataCount); | |
| o << U32LEB(wasm->dataSegments.size()); | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeDataSegments() { | |
| if (wasm->dataSegments.size() == 0) { | |
| return; | |
| } | |
| if (wasm->dataSegments.size() > WebLimitations::MaxDataSegments) { | |
| std::cerr << "Some VMs may not accept this binary because it has a large " | |
| << "number of data segments. Run the limit-segments pass to " | |
| << "merge segments.\n"; | |
| } | |
| auto start = startSection(BinaryConsts::Section::Data); | |
| o << U32LEB(wasm->dataSegments.size()); | |
| for (auto& segment : wasm->dataSegments) { | |
| uint32_t flags = 0; | |
| Index memoryIndex = 0; | |
| if (segment->isPassive()) { | |
| flags |= BinaryConsts::IsPassive; | |
| } else { | |
| memoryIndex = getMemoryIndex(segment->memory); | |
| if (memoryIndex) { | |
| flags |= BinaryConsts::HasIndex; | |
| } | |
| } | |
| o << U32LEB(flags); | |
| if (segment->isActive()) { | |
| if (memoryIndex) { | |
| o << U32LEB(memoryIndex); | |
| } | |
| writeExpression(segment->offset); | |
| o << int8_t(BinaryConsts::End); | |
| } | |
| writeInlineBuffer(segment->data.data(), segment->data.size()); | |
| } | |
| finishSection(start); | |
| } | |
| uint32_t WasmBinaryWriter::getFunctionIndex(Name name) const { | |
| auto it = indexes.functionIndexes.find(name); | |
| assert(it != indexes.functionIndexes.end()); | |
| return it->second; | |
| } | |
| uint32_t WasmBinaryWriter::getTableIndex(Name name) const { | |
| auto it = indexes.tableIndexes.find(name); | |
| assert(it != indexes.tableIndexes.end()); | |
| return it->second; | |
| } | |
| uint32_t WasmBinaryWriter::getMemoryIndex(Name name) const { | |
| auto it = indexes.memoryIndexes.find(name); | |
| assert(it != indexes.memoryIndexes.end()); | |
| return it->second; | |
| } | |
| uint32_t WasmBinaryWriter::getGlobalIndex(Name name) const { | |
| auto it = indexes.globalIndexes.find(name); | |
| assert(it != indexes.globalIndexes.end()); | |
| return it->second; | |
| } | |
| uint32_t WasmBinaryWriter::getTagIndex(Name name) const { | |
| auto it = indexes.tagIndexes.find(name); | |
| assert(it != indexes.tagIndexes.end()); | |
| return it->second; | |
| } | |
| uint32_t WasmBinaryWriter::getDataSegmentIndex(Name name) const { | |
| auto it = indexes.dataIndexes.find(name); | |
| assert(it != indexes.dataIndexes.end()); | |
| return it->second; | |
| } | |
| uint32_t WasmBinaryWriter::getElementSegmentIndex(Name name) const { | |
| auto it = indexes.elemIndexes.find(name); | |
| assert(it != indexes.elemIndexes.end()); | |
| return it->second; | |
| } | |
| uint32_t WasmBinaryWriter::getTypeIndex(HeapType type) const { | |
| auto it = indexedTypes.indices.find(type); | |
| #ifndef NDEBUG | |
| if (it == indexedTypes.indices.end()) { | |
| std::cout << "Missing type: " << type << '\n'; | |
| assert(0); | |
| } | |
| #endif | |
| return it->second; | |
| } | |
| uint32_t WasmBinaryWriter::getSignatureIndex(Signature sig) const { | |
| auto it = signatureIndexes.find(sig); | |
| #ifndef NDEBUG | |
| if (it == signatureIndexes.end()) { | |
| std::cout << "Missing signature: " << sig << '\n'; | |
| assert(0); | |
| } | |
| #endif | |
| return it->second; | |
| } | |
| uint32_t WasmBinaryWriter::getStringIndex(Name string) const { | |
| auto it = stringIndexes.find(string); | |
| assert(it != stringIndexes.end()); | |
| return it->second; | |
| } | |
| void WasmBinaryWriter::writeTableDeclarations() { | |
| if (importInfo->getNumDefinedTables() == 0) { | |
| // std::cerr << std::endl << "(WasmBinaryWriter::writeTableDeclarations) No | |
| // defined tables found. skipping" << std::endl; | |
| return; | |
| } | |
| auto start = startSection(BinaryConsts::Section::Table); | |
| auto num = importInfo->getNumDefinedTables(); | |
| o << U32LEB(num); | |
| ModuleUtils::iterDefinedTables(*wasm, [&](Table* table) { | |
| if (table->init) { | |
| o << uint8_t(BinaryConsts::HasTableInitializer); | |
| o << uint8_t(BinaryConsts::TableReservedByte); | |
| } | |
| writeType(table->type); | |
| writeResizableLimits(table->initial, | |
| table->max, | |
| table->hasMax(), | |
| /*shared=*/false, | |
| table->is64()); | |
| if (table->init) { | |
| writeExpression(table->init); | |
| o << uint8_t(BinaryConsts::End); | |
| } | |
| }); | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeElementSegments() { | |
| size_t elemCount = wasm->elementSegments.size(); | |
| auto needingElemDecl = TableUtils::getFunctionsNeedingElemDeclare(*wasm); | |
| if (!needingElemDecl.empty()) { | |
| elemCount++; | |
| } | |
| if (elemCount == 0) { | |
| return; | |
| } | |
| auto start = startSection(BinaryConsts::Section::Element); | |
| o << U32LEB(elemCount); | |
| Type funcref = Type(HeapType::func, Nullable); | |
| for (auto& segment : wasm->elementSegments) { | |
| Index tableIdx = 0; | |
| // If the segment is MVP, we can use the shorter form. | |
| bool usesExpressions = TableUtils::usesExpressions(segment.get(), wasm); | |
| // The table index can and should be elided for active segments of table 0 | |
| // when table 0 has type funcref. This was the only type of segment | |
| // supported by the MVP, which also did not support table indices in the | |
| // segment encoding. | |
| bool hasTableIndex = false; | |
| if (segment->isActive()) { | |
| tableIdx = getTableIndex(segment->table); | |
| hasTableIndex = | |
| tableIdx > 0 || wasm->getTable(segment->table)->type != funcref; | |
| } | |
| uint32_t flags = 0; | |
| if (usesExpressions) { | |
| flags |= BinaryConsts::UsesExpressions; | |
| } | |
| if (segment->isPassive()) { | |
| flags |= BinaryConsts::IsPassive; | |
| } else if (hasTableIndex) { | |
| flags |= BinaryConsts::HasIndex; | |
| } | |
| o << U32LEB(flags); | |
| if (segment->isActive()) { | |
| if (hasTableIndex) { | |
| o << U32LEB(tableIdx); | |
| } | |
| writeExpression(segment->offset); | |
| o << int8_t(BinaryConsts::End); | |
| } | |
| if (segment->isPassive() || hasTableIndex) { | |
| if (usesExpressions) { | |
| // elemType | |
| writeType(segment->type); | |
| } else { | |
| // MVP elemKind of funcref | |
| o << U32LEB(0); | |
| } | |
| } | |
| o << U32LEB(segment->data.size()); | |
| if (usesExpressions) { | |
| for (auto* item : segment->data) { | |
| writeExpression(item); | |
| o << int8_t(BinaryConsts::End); | |
| } | |
| } else { | |
| for (auto& item : segment->data) { | |
| // We've ensured that all items are ref.func. | |
| auto& name = item->cast<RefFunc>()->func; | |
| o << U32LEB(getFunctionIndex(name)); | |
| } | |
| } | |
| } | |
| if (!needingElemDecl.empty()) { | |
| o << U32LEB(BinaryConsts::IsPassive | BinaryConsts::IsDeclarative); | |
| o << U32LEB(0); // type (indicating funcref) | |
| o << U32LEB(needingElemDecl.size()); | |
| for (auto name : needingElemDecl) { | |
| o << U32LEB(indexes.functionIndexes[name]); | |
| } | |
| } | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeTags() { | |
| if (importInfo->getNumDefinedTags() == 0) { | |
| return; | |
| } | |
| auto start = startSection(BinaryConsts::Section::Tag); | |
| auto num = importInfo->getNumDefinedTags(); | |
| o << U32LEB(num); | |
| ModuleUtils::iterDefinedTags(*wasm, [&](Tag* tag) { | |
| o << uint8_t(0); // Reserved 'attribute' field. Always 0. | |
| o << U32LEB(getTypeIndex(tag->type)); | |
| }); | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeNames() { | |
| auto start = startSection(BinaryConsts::Section::Custom); | |
| writeInlineString(BinaryConsts::CustomSections::Name); | |
| // module name | |
| if (emitModuleName && wasm->name.is()) { | |
| auto substart = | |
| startSubsection(BinaryConsts::CustomSections::Subsection::NameModule); | |
| writeInlineString(wasm->name.view()); | |
| finishSubsection(substart); | |
| } | |
| if (!debugInfo) { | |
| // We were only writing the module name. | |
| finishSection(start); | |
| return; | |
| } | |
| // function names | |
| { | |
| std::vector<std::pair<Index, Function*>> functionsWithNames; | |
| Index checked = 0; | |
| auto check = [&](Function* curr) { | |
| if (curr->hasExplicitName) { | |
| functionsWithNames.push_back({checked, curr}); | |
| } | |
| checked++; | |
| }; | |
| ModuleUtils::iterImportedFunctions(*wasm, check); | |
| ModuleUtils::iterDefinedFunctions(*wasm, check); | |
| assert(checked == indexes.functionIndexes.size()); | |
| if (functionsWithNames.size() > 0) { | |
| auto substart = | |
| startSubsection(BinaryConsts::CustomSections::Subsection::NameFunction); | |
| o << U32LEB(functionsWithNames.size()); | |
| for (auto& [index, global] : functionsWithNames) { | |
| o << U32LEB(index); | |
| writeInlineString(global->name.view()); | |
| } | |
| finishSubsection(substart); | |
| } | |
| } | |
| // local names | |
| { | |
| // Find all functions with at least one local name and only emit the | |
| // subsection if there is at least one. | |
| std::vector<std::pair<Index, Function*>> functionsWithLocalNames; | |
| Index checked = 0; | |
| auto check = [&](Function* curr) { | |
| auto numLocals = curr->getNumLocals(); | |
| for (Index i = 0; i < numLocals; ++i) { | |
| if (curr->hasLocalName(i)) { | |
| functionsWithLocalNames.push_back({checked, curr}); | |
| break; | |
| } | |
| } | |
| checked++; | |
| }; | |
| ModuleUtils::iterImportedFunctions(*wasm, check); | |
| ModuleUtils::iterDefinedFunctions(*wasm, check); | |
| assert(checked == indexes.functionIndexes.size()); | |
| if (functionsWithLocalNames.size() > 0) { | |
| // Otherwise emit those functions but only include locals with a name. | |
| auto substart = | |
| startSubsection(BinaryConsts::CustomSections::Subsection::NameLocal); | |
| o << U32LEB(functionsWithLocalNames.size()); | |
| [[maybe_unused]] Index emitted = 0; | |
| for (auto& [index, func] : functionsWithLocalNames) { | |
| // Pairs of (local index in IR, name). | |
| std::vector<std::pair<Index, Name>> localsWithNames; | |
| auto numLocals = func->getNumLocals(); | |
| for (Index indexInFunc = 0; indexInFunc < numLocals; ++indexInFunc) { | |
| if (func->hasLocalName(indexInFunc)) { | |
| Index indexInBinary; | |
| auto iter = funcMappedLocals.find(func->name); | |
| if (iter != funcMappedLocals.end()) { | |
| // TODO: handle multivalue | |
| indexInBinary = iter->second[{indexInFunc, 0}]; | |
| } else { | |
| // No data on funcMappedLocals. That is only possible if we are an | |
| // imported function, where there are no locals to map, and in | |
| // that case the index is unchanged anyhow: parameters always have | |
| // the same index, they are not mapped in any way. | |
| assert(func->imported()); | |
| indexInBinary = indexInFunc; | |
| } | |
| localsWithNames.push_back( | |
| {indexInBinary, func->getLocalName(indexInFunc)}); | |
| } | |
| } | |
| assert(localsWithNames.size()); | |
| std::sort(localsWithNames.begin(), localsWithNames.end()); | |
| o << U32LEB(index); | |
| o << U32LEB(localsWithNames.size()); | |
| for (auto& [indexInBinary, name] : localsWithNames) { | |
| o << U32LEB(indexInBinary); | |
| writeInlineString(name.view()); | |
| } | |
| emitted++; | |
| } | |
| assert(emitted == functionsWithLocalNames.size()); | |
| finishSubsection(substart); | |
| } | |
| } | |
| // label names | |
| { | |
| std::vector<std::pair<Index, Function*>> functionsWithLabelNames; | |
| Index checked = 0; | |
| auto check = [&](Function* curr) { | |
| if (funcLabelNames.count(curr->name)) { | |
| functionsWithLabelNames.push_back({checked, curr}); | |
| } | |
| checked++; | |
| }; | |
| ModuleUtils::iterImportedFunctions(*wasm, check); | |
| ModuleUtils::iterDefinedFunctions(*wasm, check); | |
| assert(checked == indexes.functionIndexes.size()); | |
| if (functionsWithLabelNames.size() > 0) { | |
| auto substart = | |
| startSubsection(BinaryConsts::CustomSections::Subsection::NameLabel); | |
| o << U32LEB(functionsWithLabelNames.size()); | |
| for (auto& [index, func] : functionsWithLabelNames) { | |
| // The labels were gathered in the order they were emitted, which is the | |
| // order of the label index space. | |
| auto& labels = funcLabelNames[func->name]; | |
| o << U32LEB(index); | |
| o << U32LEB(labels.size()); | |
| for (auto& [labelIndex, name] : labels) { | |
| o << U32LEB(labelIndex); | |
| writeInlineString(name.view()); | |
| } | |
| } | |
| finishSubsection(substart); | |
| } | |
| } | |
| // type names | |
| { | |
| std::vector<HeapType> namedTypes; | |
| for (auto type : indexedTypes.types) { | |
| if (wasm->typeNames.contains(type) && wasm->typeNames[type].name.is()) { | |
| namedTypes.push_back(type); | |
| } | |
| } | |
| if (!namedTypes.empty()) { | |
| auto substart = | |
| startSubsection(BinaryConsts::CustomSections::Subsection::NameType); | |
| o << U32LEB(namedTypes.size()); | |
| for (auto type : namedTypes) { | |
| o << U32LEB(indexedTypes.indices[type]); | |
| writeInlineString(wasm->typeNames[type].name.view()); | |
| } | |
| finishSubsection(substart); | |
| } | |
| } | |
| // table names | |
| { | |
| std::vector<std::pair<Index, Table*>> tablesWithNames; | |
| Index checked = 0; | |
| auto check = [&](Table* curr) { | |
| if (curr->hasExplicitName) { | |
| tablesWithNames.push_back({checked, curr}); | |
| } | |
| checked++; | |
| }; | |
| ModuleUtils::iterImportedTables(*wasm, check); | |
| ModuleUtils::iterDefinedTables(*wasm, check); | |
| assert(checked == indexes.tableIndexes.size()); | |
| if (tablesWithNames.size() > 0) { | |
| auto substart = | |
| startSubsection(BinaryConsts::CustomSections::Subsection::NameTable); | |
| o << U32LEB(tablesWithNames.size()); | |
| for (auto& [index, table] : tablesWithNames) { | |
| o << U32LEB(index); | |
| writeInlineString(table->name.view()); | |
| } | |
| finishSubsection(substart); | |
| } | |
| } | |
| // memory names | |
| { | |
| std::vector<std::pair<Index, Memory*>> memoriesWithNames; | |
| Index checked = 0; | |
| auto check = [&](Memory* curr) { | |
| if (curr->hasExplicitName) { | |
| memoriesWithNames.push_back({checked, curr}); | |
| } | |
| checked++; | |
| }; | |
| ModuleUtils::iterImportedMemories(*wasm, check); | |
| ModuleUtils::iterDefinedMemories(*wasm, check); | |
| assert(checked == indexes.memoryIndexes.size()); | |
| if (memoriesWithNames.size() > 0) { | |
| auto substart = | |
| startSubsection(BinaryConsts::CustomSections::Subsection::NameMemory); | |
| o << U32LEB(memoriesWithNames.size()); | |
| for (auto& [index, memory] : memoriesWithNames) { | |
| o << U32LEB(index); | |
| writeInlineString(memory->name.view()); | |
| } | |
| finishSubsection(substart); | |
| } | |
| } | |
| // global names | |
| { | |
| std::vector<std::pair<Index, Global*>> globalsWithNames; | |
| Index checked = 0; | |
| auto check = [&](Global* curr) { | |
| if (curr->hasExplicitName) { | |
| globalsWithNames.push_back({checked, curr}); | |
| } | |
| checked++; | |
| }; | |
| ModuleUtils::iterImportedGlobals(*wasm, check); | |
| ModuleUtils::iterDefinedGlobals(*wasm, check); | |
| assert(checked == indexes.globalIndexes.size()); | |
| if (globalsWithNames.size() > 0) { | |
| auto substart = | |
| startSubsection(BinaryConsts::CustomSections::Subsection::NameGlobal); | |
| o << U32LEB(globalsWithNames.size()); | |
| for (auto& [index, global] : globalsWithNames) { | |
| o << U32LEB(index); | |
| writeInlineString(global->name.view()); | |
| } | |
| finishSubsection(substart); | |
| } | |
| } | |
| // elem segment names | |
| { | |
| std::vector<std::pair<Index, ElementSegment*>> elemsWithNames; | |
| Index checked = 0; | |
| for (auto& curr : wasm->elementSegments) { | |
| if (curr->hasExplicitName) { | |
| elemsWithNames.push_back({checked, curr.get()}); | |
| } | |
| checked++; | |
| } | |
| assert(checked == indexes.elemIndexes.size()); | |
| if (elemsWithNames.size() > 0) { | |
| auto substart = | |
| startSubsection(BinaryConsts::CustomSections::Subsection::NameElem); | |
| o << U32LEB(elemsWithNames.size()); | |
| for (auto& [index, elem] : elemsWithNames) { | |
| o << U32LEB(index); | |
| writeInlineString(elem->name.view()); | |
| } | |
| finishSubsection(substart); | |
| } | |
| } | |
| // data segment names | |
| { | |
| Index count = 0; | |
| for (auto& seg : wasm->dataSegments) { | |
| if (seg->hasExplicitName) { | |
| count++; | |
| } | |
| } | |
| if (count) { | |
| auto substart = | |
| startSubsection(BinaryConsts::CustomSections::Subsection::NameData); | |
| o << U32LEB(count); | |
| for (Index i = 0; i < wasm->dataSegments.size(); i++) { | |
| auto& seg = wasm->dataSegments[i]; | |
| if (seg->hasExplicitName) { | |
| o << U32LEB(i); | |
| writeInlineString(seg->name.view()); | |
| } | |
| } | |
| finishSubsection(substart); | |
| } | |
| } | |
| // TODO: label, type, and element names | |
| // see: https://github.com/WebAssembly/extended-name-section | |
| // GC field names | |
| if (wasm->features.hasGC()) { | |
| std::vector<HeapType> relevantTypes; | |
| for (auto& type : indexedTypes.types) { | |
| if (type.isStruct() && wasm->typeNames.contains(type) && | |
| !wasm->typeNames[type].fieldNames.empty()) { | |
| relevantTypes.push_back(type); | |
| } | |
| } | |
| if (!relevantTypes.empty()) { | |
| auto substart = | |
| startSubsection(BinaryConsts::CustomSections::Subsection::NameField); | |
| o << U32LEB(relevantTypes.size()); | |
| for (Index i = 0; i < relevantTypes.size(); i++) { | |
| auto type = relevantTypes[i]; | |
| o << U32LEB(indexedTypes.indices[type]); | |
| std::unordered_map<Index, Name>& fieldNames = | |
| wasm->typeNames.at(type).fieldNames; | |
| o << U32LEB(fieldNames.size()); | |
| for (auto& [index, name] : fieldNames) { | |
| o << U32LEB(index); | |
| writeInlineString(name.view()); | |
| } | |
| } | |
| finishSubsection(substart); | |
| } | |
| } | |
| // tag names | |
| { | |
| std::vector<std::pair<Index, Tag*>> tagsWithNames; | |
| Index checked = 0; | |
| auto check = [&](Tag* curr) { | |
| if (curr->hasExplicitName) { | |
| tagsWithNames.push_back({checked, curr}); | |
| } | |
| checked++; | |
| }; | |
| ModuleUtils::iterImportedTags(*wasm, check); | |
| ModuleUtils::iterDefinedTags(*wasm, check); | |
| assert(checked == indexes.tagIndexes.size()); | |
| if (tagsWithNames.size() > 0) { | |
| auto substart = | |
| startSubsection(BinaryConsts::CustomSections::Subsection::NameTag); | |
| o << U32LEB(tagsWithNames.size()); | |
| for (auto& [index, tag] : tagsWithNames) { | |
| o << U32LEB(index); | |
| writeInlineString(tag->name.view()); | |
| } | |
| finishSubsection(substart); | |
| } | |
| } | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeSourceMapUrl() { | |
| auto start = startSection(BinaryConsts::Section::Custom); | |
| writeInlineString(BinaryConsts::CustomSections::SourceMapUrl); | |
| writeInlineString(sourceMapUrl.c_str()); | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeSymbolMap() { | |
| std::ofstream file(symbolMap); | |
| auto write = [&](Function* func) { | |
| file << getFunctionIndex(func->name) << ":" << func->name.view() | |
| << std::endl; | |
| }; | |
| ModuleUtils::iterImportedFunctions(*wasm, write); | |
| ModuleUtils::iterDefinedFunctions(*wasm, write); | |
| file.close(); | |
| } | |
| void WasmBinaryWriter::initializeDebugInfo() { | |
| lastDebugLocation = {0, /* lineNumber = */ 1, 0, std::nullopt}; | |
| } | |
| void WasmBinaryWriter::writeSourceMapProlog() { | |
| *sourceMap << "{\"version\":3,"; | |
| for (const auto& section : wasm->customSections) { | |
| if (section.name == BinaryConsts::CustomSections::BuildId) { | |
| U32LEB ret; | |
| size_t pos = 0; | |
| ret.read([&]() { return section.data[pos++]; }); | |
| if (section.data.size() != pos + ret.value) { | |
| std::cerr | |
| << "warning: build id section with an incorrect size detected!\n"; | |
| break; | |
| } | |
| *sourceMap << "\"debugId\":\""; | |
| for (size_t i = pos; i < section.data.size(); i++) { | |
| *sourceMap << std::setfill('0') << std::setw(2) << std::hex | |
| << static_cast<int>(static_cast<uint8_t>(section.data[i])); | |
| } | |
| *sourceMap << "\","; | |
| break; | |
| } | |
| } | |
| // Remove unused function names from 'names' field. | |
| if (!wasm->debugInfoSymbolNames.empty()) { | |
| std::vector<std::string> newSymbolNames; | |
| std::map<Index, Index> oldToNewIndex; | |
| // Collect all used symbol name indexes. | |
| auto prepareIndexMap = | |
| [&](const std::optional<Function::DebugLocation>& location) { | |
| if (location && location->symbolNameIndex) { | |
| uint32_t oldIndex = *location->symbolNameIndex; | |
| assert(oldIndex < wasm->debugInfoSymbolNames.size()); | |
| oldToNewIndex[oldIndex] = 0; // placeholder | |
| } | |
| }; | |
| for (auto& func : wasm->functions) { | |
| for (auto& [_, location] : func->debugLocations) { | |
| prepareIndexMap(location); | |
| } | |
| prepareIndexMap(func->prologLocation); | |
| prepareIndexMap(func->epilogLocation); | |
| } | |
| // Create the new list of names and the mapping from old to new indices. | |
| uint32_t index = 0; | |
| for (auto& [oldIndex, newIndex] : oldToNewIndex) { | |
| newSymbolNames.push_back(wasm->debugInfoSymbolNames[oldIndex]); | |
| newIndex = index++; | |
| } | |
| // Update all debug locations to point to the new indices. | |
| auto updateIndex = [&](std::optional<Function::DebugLocation>& location) { | |
| if (location && location->symbolNameIndex) { | |
| uint32_t oldIndex = *location->symbolNameIndex; | |
| location->symbolNameIndex = oldToNewIndex[oldIndex]; | |
| } | |
| }; | |
| for (auto& func : wasm->functions) { | |
| for (auto& [_, location] : func->debugLocations) { | |
| updateIndex(location); | |
| } | |
| updateIndex(func->prologLocation); | |
| updateIndex(func->epilogLocation); | |
| } | |
| // Replace the old symbol names with the new, pruned list. | |
| wasm->debugInfoSymbolNames = std::move(newSymbolNames); | |
| } | |
| auto writeOptionalString = [&](const char* name, const std::string& str) { | |
| if (!str.empty()) { | |
| *sourceMap << "\"" << name << "\":\"" << str << "\","; | |
| } | |
| }; | |
| writeOptionalString("file", wasm->debugInfoFile); | |
| writeOptionalString("sourceRoot", wasm->debugInfoSourceRoot); | |
| auto writeStringVector = [&](const char* name, | |
| const std::vector<std::string>& vec) { | |
| *sourceMap << "\"" << name << "\":["; | |
| for (size_t i = 0; i < vec.size(); i++) { | |
| if (i > 0) { | |
| *sourceMap << ","; | |
| } | |
| *sourceMap << "\"" << vec[i] << "\""; | |
| } | |
| *sourceMap << "],"; | |
| }; | |
| writeStringVector("sources", wasm->debugInfoFileNames); | |
| if (!wasm->debugInfoSourcesContent.empty()) { | |
| writeStringVector("sourcesContent", wasm->debugInfoSourcesContent); | |
| } | |
| writeStringVector("names", wasm->debugInfoSymbolNames); | |
| *sourceMap << "\"mappings\":\""; | |
| } | |
| static void writeBase64VLQ(std::ostream& out, int32_t n) { | |
| uint32_t value = n >= 0 ? n << 1 : ((-n) << 1) | 1; | |
| while (1) { | |
| uint32_t digit = value & 0x1F; | |
| value >>= 5; | |
| if (!value) { | |
| // last VLQ digit -- base64 codes 'A'..'Z', 'a'..'f' | |
| out << char(digit < 26 ? 'A' + digit : 'a' + digit - 26); | |
| break; | |
| } | |
| // more VLG digit will follow -- add continuation bit (0x20), | |
| // base64 codes 'g'..'z', '0'..'9', '+', '/' | |
| out << char(digit < 20 ? 'g' + digit | |
| : digit < 30 ? '0' + digit - 20 | |
| : digit == 30 ? '+' | |
| : '/'); | |
| } | |
| } | |
| void WasmBinaryWriter::writeSourceMapEpilog() { | |
| // write source map entries | |
| size_t lastOffset = 0; | |
| BinaryLocation lastFileIndex = 0; | |
| BinaryLocation lastLineNumber = 1; | |
| BinaryLocation lastColumnNumber = 0; | |
| BinaryLocation lastSymbolNameIndex = 0; | |
| for (const auto& [offset, loc] : sourceMapLocations) { | |
| if (lastOffset > 0) { | |
| *sourceMap << ","; | |
| } | |
| writeBase64VLQ(*sourceMap, int32_t(offset - lastOffset)); | |
| lastOffset = offset; | |
| if (loc) { | |
| writeBase64VLQ(*sourceMap, int32_t(loc->fileIndex - lastFileIndex)); | |
| lastFileIndex = loc->fileIndex; | |
| writeBase64VLQ(*sourceMap, int32_t(loc->lineNumber - lastLineNumber)); | |
| lastLineNumber = loc->lineNumber; | |
| writeBase64VLQ(*sourceMap, int32_t(loc->columnNumber - lastColumnNumber)); | |
| lastColumnNumber = loc->columnNumber; | |
| if (loc->symbolNameIndex) { | |
| writeBase64VLQ(*sourceMap, | |
| int32_t(*loc->symbolNameIndex - lastSymbolNameIndex)); | |
| lastSymbolNameIndex = *loc->symbolNameIndex; | |
| } | |
| } | |
| } | |
| *sourceMap << "\"}"; | |
| } | |
| void WasmBinaryWriter::writeLateCustomSections() { | |
| for (auto& section : wasm->customSections) { | |
| if (section.name == BinaryConsts::CustomSections::Dylink) { | |
| // This is an early custom section. | |
| continue; | |
| } | |
| if (section.name == BinaryConsts::CustomSections::SourceMapUrl && | |
| sourceMap && !sourceMapUrl.empty()) { | |
| // We are writing a SourceMapURL manually, following the user's request. | |
| // Do not emit the existing custom section as a second one. | |
| continue; | |
| } | |
| writeCustomSection(section); | |
| } | |
| } | |
| void WasmBinaryWriter::writeCustomSection(const CustomSection& section) { | |
| auto start = startSection(BinaryConsts::Custom); | |
| writeInlineString(section.name.c_str()); | |
| for (size_t i = 0; i < section.data.size(); i++) { | |
| o << uint8_t(section.data[i]); | |
| } | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeFeaturesSection() { | |
| if (!wasm->hasFeaturesSection || wasm->features.isMVP()) { | |
| return; | |
| } | |
| // TODO(tlively): unify feature names with rest of toolchain and use | |
| // FeatureSet::toString() | |
| auto toString = [](FeatureSet::Feature f) { | |
| switch (f) { | |
| case FeatureSet::Atomics: | |
| return BinaryConsts::CustomSections::AtomicsFeature; | |
| case FeatureSet::MutableGlobals: | |
| return BinaryConsts::CustomSections::MutableGlobalsFeature; | |
| case FeatureSet::TruncSat: | |
| return BinaryConsts::CustomSections::TruncSatFeature; | |
| case FeatureSet::Multibyte: | |
| return BinaryConsts::CustomSections::MultibyteFeature; | |
| case FeatureSet::SIMD: | |
| return BinaryConsts::CustomSections::SIMD128Feature; | |
| case FeatureSet::BulkMemory: | |
| return BinaryConsts::CustomSections::BulkMemoryFeature; | |
| case FeatureSet::SignExt: | |
| return BinaryConsts::CustomSections::SignExtFeature; | |
| case FeatureSet::ExceptionHandling: | |
| return BinaryConsts::CustomSections::ExceptionHandlingFeature; | |
| case FeatureSet::TailCall: | |
| return BinaryConsts::CustomSections::TailCallFeature; | |
| case FeatureSet::ReferenceTypes: | |
| return BinaryConsts::CustomSections::ReferenceTypesFeature; | |
| case FeatureSet::Multivalue: | |
| return BinaryConsts::CustomSections::MultivalueFeature; | |
| case FeatureSet::GC: | |
| return BinaryConsts::CustomSections::GCFeature; | |
| case FeatureSet::Memory64: | |
| return BinaryConsts::CustomSections::Memory64Feature; | |
| case FeatureSet::RelaxedSIMD: | |
| return BinaryConsts::CustomSections::RelaxedSIMDFeature; | |
| case FeatureSet::ExtendedConst: | |
| return BinaryConsts::CustomSections::ExtendedConstFeature; | |
| case FeatureSet::Strings: | |
| return BinaryConsts::CustomSections::StringsFeature; | |
| case FeatureSet::MultiMemory: | |
| return BinaryConsts::CustomSections::MultiMemoryFeature; | |
| case FeatureSet::StackSwitching: | |
| return BinaryConsts::CustomSections::StackSwitchingFeature; | |
| case FeatureSet::SharedEverything: | |
| return BinaryConsts::CustomSections::SharedEverythingFeature; | |
| case FeatureSet::FP16: | |
| return BinaryConsts::CustomSections::FP16Feature; | |
| case FeatureSet::BulkMemoryOpt: | |
| return BinaryConsts::CustomSections::BulkMemoryOptFeature; | |
| case FeatureSet::CallIndirectOverlong: | |
| return BinaryConsts::CustomSections::CallIndirectOverlongFeature; | |
| case FeatureSet::CustomDescriptors: | |
| return BinaryConsts::CustomSections::CustomDescriptorsFeature; | |
| case FeatureSet::AcquireReleaseAtomics: | |
| return BinaryConsts::CustomSections::AcquireReleaseAtomicsFeature; | |
| case FeatureSet::CustomPageSizes: | |
| return BinaryConsts::CustomSections::CustomPageSizesFeature; | |
| case FeatureSet::WideArithmetic: | |
| return BinaryConsts::CustomSections::WideArithmeticFeature; | |
| case FeatureSet::CompactImports: | |
| return BinaryConsts::CustomSections::CompactImportsFeature; | |
| case FeatureSet::RelaxedAtomics: | |
| return BinaryConsts::CustomSections::RelaxedAtomicsFeature; | |
| case FeatureSet::None: | |
| case FeatureSet::Default: | |
| case FeatureSet::All: | |
| break; | |
| } | |
| WASM_UNREACHABLE("unexpected feature flag"); | |
| }; | |
| std::vector<const char*> features; | |
| wasm->features.iterFeatures( | |
| [&](FeatureSet::Feature f) { features.push_back(toString(f)); }); | |
| auto start = startSection(BinaryConsts::Custom); | |
| writeInlineString(BinaryConsts::CustomSections::TargetFeatures); | |
| o << U32LEB(features.size()); | |
| for (auto& f : features) { | |
| o << uint8_t(BinaryConsts::FeatureUsed); | |
| writeInlineString(f); | |
| } | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeLegacyDylinkSection() { | |
| if (!wasm->dylinkSection) { | |
| return; | |
| } | |
| auto start = startSection(BinaryConsts::Custom); | |
| writeInlineString(BinaryConsts::CustomSections::Dylink); | |
| o << U32LEB(wasm->dylinkSection->memorySize); | |
| o << U32LEB(wasm->dylinkSection->memoryAlignment); | |
| o << U32LEB(wasm->dylinkSection->tableSize); | |
| o << U32LEB(wasm->dylinkSection->tableAlignment); | |
| o << U32LEB(wasm->dylinkSection->neededDynlibs.size()); | |
| for (auto& neededDynlib : wasm->dylinkSection->neededDynlibs) { | |
| writeInlineString(neededDynlib.view()); | |
| } | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeDylinkSection() { | |
| if (!wasm->dylinkSection) { | |
| return; | |
| } | |
| if (wasm->dylinkSection->isLegacy) { | |
| writeLegacyDylinkSection(); | |
| return; | |
| } | |
| auto start = startSection(BinaryConsts::Custom); | |
| writeInlineString(BinaryConsts::CustomSections::Dylink0); | |
| auto substart = | |
| startSubsection(BinaryConsts::CustomSections::Subsection::DylinkMemInfo); | |
| o << U32LEB(wasm->dylinkSection->memorySize); | |
| o << U32LEB(wasm->dylinkSection->memoryAlignment); | |
| o << U32LEB(wasm->dylinkSection->tableSize); | |
| o << U32LEB(wasm->dylinkSection->tableAlignment); | |
| finishSubsection(substart); | |
| if (wasm->dylinkSection->neededDynlibs.size()) { | |
| substart = | |
| startSubsection(BinaryConsts::CustomSections::Subsection::DylinkNeeded); | |
| o << U32LEB(wasm->dylinkSection->neededDynlibs.size()); | |
| for (auto& neededDynlib : wasm->dylinkSection->neededDynlibs) { | |
| writeInlineString(neededDynlib.view()); | |
| } | |
| finishSubsection(substart); | |
| } | |
| writeData(wasm->dylinkSection->tail.data(), wasm->dylinkSection->tail.size()); | |
| finishSection(start); | |
| } | |
| void WasmBinaryWriter::writeDebugLocation(const Function::DebugLocation& loc) { | |
| if (loc == lastDebugLocation) { | |
| return; | |
| } | |
| auto offset = o.size(); | |
| sourceMapLocations.emplace_back(offset, &loc); | |
| lastDebugLocation = loc; | |
| } | |
| void WasmBinaryWriter::writeNoDebugLocation() { | |
| // Emit an indication that there is no debug location there (so that | |
| // we do not get "smeared" with debug info from anything before or | |
| // after us). | |
| // | |
| // We don't need to write repeated "no debug info" indications, as a | |
| // single one is enough to make it clear that the debug information | |
| // before us is valid no longer. We also don't need to write one if | |
| // there is nothing before us. | |
| if (!sourceMapLocations.empty() && | |
| sourceMapLocations.back().second != nullptr) { | |
| sourceMapLocations.emplace_back(o.size(), nullptr); | |
| // Initialize the state of debug info to indicate there is no current | |
| // debug info relevant. This sets |lastDebugLocation| to a dummy value, | |
| // so that later places with debug info can see that they differ from | |
| // it (without this, if we had some debug info, then a nullptr for none, | |
| // and then the same debug info, we could get confused). | |
| initializeDebugInfo(); | |
| } | |
| } | |
| void WasmBinaryWriter::writeSourceMapLocation(Expression* curr, | |
| Function* func) { | |
| assert(sourceMap); | |
| auto& debugLocations = func->debugLocations; | |
| auto iter = debugLocations.find(curr); | |
| if (iter != debugLocations.end() && iter->second) { | |
| // There is debug information here, write it out. | |
| writeDebugLocation(*(iter->second)); | |
| } else { | |
| // This expression has no debug location. | |
| writeNoDebugLocation(); | |
| } | |
| } | |
| void WasmBinaryWriter::trackExpressionStart(Expression* curr, Function* func) { | |
| // If this is an instruction in a function, and if the original wasm had | |
| // binary locations tracked, then track it in the output as well. We also | |
| // track locations of instructions that have code annotations, as their binary | |
| // location goes in the custom section. | |
| if (func && (!func->expressionLocations.empty() || | |
| func->codeAnnotations.contains(curr))) { | |
| binaryLocations.expressions[curr] = | |
| BinaryLocations::Span{BinaryLocation(o.size()), 0}; | |
| binaryLocationTrackedExpressionsForFunc.push_back(curr); | |
| } | |
| } | |
| void WasmBinaryWriter::trackExpressionEnd(Expression* curr, Function* func) { | |
| // TODO: If we need to track the end of annotated code locations, we need to | |
| // enable that here. | |
| if (func && !func->expressionLocations.empty()) { | |
| auto& span = binaryLocations.expressions.at(curr); | |
| span.end = o.size(); | |
| } | |
| } | |
| void WasmBinaryWriter::trackExpressionDelimiter(Expression* curr, | |
| Function* func, | |
| size_t id) { | |
| // TODO: If we need to track the delimiters of annotated code locations, we | |
| // need to enable that here. | |
| if (func && !func->expressionLocations.empty()) { | |
| binaryLocations.delimiters[curr][id] = o.size(); | |
| } | |
| } | |
| std::optional<BufferWithRandomAccess> WasmBinaryWriter::writeCodeAnnotations() { | |
| std::optional<BufferWithRandomAccess> ret; | |
| auto append = [&](std::optional<BufferWithRandomAccess>&& temp) { | |
| if (temp) { | |
| if (!ret) { | |
| // This is the first section. | |
| ret = std::move(temp); | |
| } else { | |
| // This is a later section, append. | |
| ret->insert(ret->end(), temp->begin(), temp->end()); | |
| } | |
| } | |
| }; | |
| append(getBranchHintsBuffer()); | |
| append(getInlineHintsBuffer()); | |
| append(getRemovableIfUnusedHintsBuffer()); | |
| append(getJSCalledHintsBuffer()); | |
| append(getIdempotentHintsBuffer()); | |
| append(getToolchainInlineHintsBuffer()); | |
| append(getNoReturnHintsBuffer()); | |
| return ret; | |
| } | |
| template<typename HasFunc, typename EmitFunc> | |
| std::optional<BufferWithRandomAccess> WasmBinaryWriter::writeExpressionHints( | |
| Name sectionName, HasFunc has, EmitFunc emit) { | |
| // Assemble the info: for each function, a vector of the hints. | |
| struct ExprHint { | |
| Expression* expr; | |
| // The offset we will write in the custom section. | |
| BinaryLocation offset; | |
| CodeAnnotation* hint; | |
| }; | |
| struct FuncHints { | |
| Name func; | |
| std::vector<ExprHint> exprHints; | |
| }; | |
| std::vector<FuncHints> funcHintsVec; | |
| for (auto& func : wasm->functions) { | |
| // Collect the hints for this function. | |
| FuncHints funcHints; | |
| // We compute the location of the function declaration area (where the | |
| // locals are declared) the first time we need it. | |
| BinaryLocation funcDeclarationsOffset = 0; | |
| for (auto& [expr, annotation] : func->codeAnnotations) { | |
| if (has(annotation)) { | |
| auto exprIter = binaryLocations.expressions.find(expr); | |
| if (exprIter == binaryLocations.expressions.end()) { | |
| // No expression exists for this annotation - perhaps optimizations | |
| // removed it. | |
| continue; | |
| } | |
| auto exprOffset = exprIter->second.start; | |
| if (!funcDeclarationsOffset) { | |
| auto funcIter = binaryLocations.functions.find(func.get()); | |
| assert(funcIter != binaryLocations.functions.end()); | |
| funcDeclarationsOffset = funcIter->second.declarations; | |
| } | |
| // Compute the offset: it should be relative to the start of the | |
| // function locals (i.e. the function declarations). | |
| auto offset = exprOffset - funcDeclarationsOffset; | |
| funcHints.exprHints.push_back(ExprHint{expr, offset, &annotation}); | |
| } | |
| } | |
| auto& funcAnnotations = func->funcAnnotations; | |
| if (has(funcAnnotations)) { | |
| funcHints.exprHints.push_back(ExprHint{nullptr, 0, &funcAnnotations}); | |
| } | |
| if (funcHints.exprHints.empty()) { | |
| continue; | |
| } | |
| // We found something. Finalize the data. | |
| funcHints.func = func->name; | |
| // Hints must be sorted by increasing binary offset. | |
| std::sort( | |
| funcHints.exprHints.begin(), | |
| funcHints.exprHints.end(), | |
| [](const ExprHint& a, const ExprHint& b) { return a.offset < b.offset; }); | |
| funcHintsVec.emplace_back(std::move(funcHints)); | |
| } | |
| if (funcHintsVec.empty()) { | |
| return {}; | |
| } | |
| BufferWithRandomAccess buffer; | |
| // We found data: emit the section. | |
| buffer << uint8_t(BinaryConsts::Custom); | |
| auto lebPos = buffer.writeU32LEBPlaceholder(); | |
| buffer.writeInlineString(sectionName.view()); | |
| buffer << U32LEB(funcHintsVec.size()); | |
| for (auto& funcHints : funcHintsVec) { | |
| buffer << U32LEB(getFunctionIndex(funcHints.func)); | |
| buffer << U32LEB(funcHints.exprHints.size()); | |
| for (auto& exprHint : funcHints.exprHints) { | |
| buffer << U32LEB(exprHint.offset); | |
| emit(*exprHint.hint, buffer); | |
| } | |
| } | |
| // Write the final size. We can ignore the return value, which is the number | |
| // of bytes we shrank (if the LEB was smaller than the maximum size), as no | |
| // value in this section cares. | |
| buffer.emitRetroactiveSectionSizeLEB(lebPos); | |
| return buffer; | |
| } | |
| std::optional<BufferWithRandomAccess> WasmBinaryWriter::getBranchHintsBuffer() { | |
| return writeExpressionHints( | |
| Annotations::BranchHint, | |
| [](const CodeAnnotation& annotation) { return annotation.branchLikely; }, | |
| [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { | |
| // Hint size, always 1 for now. | |
| buffer << U32LEB(1); | |
| // We must only emit hints that are present. | |
| assert(annotation.branchLikely); | |
| // Hint contents: likely or not. | |
| buffer << U32LEB(int(*annotation.branchLikely)); | |
| }); | |
| } | |
| // Writes a simple i7 hint, in the range [0..127]. | |
| #define WRITE_I7_HINT(code, field) \ | |
| return writeExpressionHints( \ | |
| code, \ | |
| [](const CodeAnnotation& annotation) { return annotation.field; }, \ | |
| [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { \ | |
| /* Hint size, always 1 for now. */ \ | |
| buffer << U32LEB(1); \ | |
| /* We must only emit hints that are present. */ \ | |
| assert(annotation.field); \ | |
| /* Hint must fit in one byte. */ \ | |
| assert(*annotation.field <= 127); \ | |
| /* Hint contents: inline frequency count. */ \ | |
| buffer << U32LEB(*annotation.field); \ | |
| }); | |
| std::optional<BufferWithRandomAccess> WasmBinaryWriter::getInlineHintsBuffer() { | |
| WRITE_I7_HINT(Annotations::InlineHint, inline_); | |
| } | |
| // Writes a simple boolean hint of size 0. Receives the code and the field name | |
| // on the annotation object. | |
| #define WRITE_BOOLEAN_HINT(code, field) \ | |
| return writeExpressionHints( \ | |
| code, \ | |
| [](const CodeAnnotation& annotation) { return annotation.field; }, \ | |
| [](const CodeAnnotation& annotation, BufferWithRandomAccess& buffer) { \ | |
| buffer << U32LEB(0); \ | |
| }); | |
| std::optional<BufferWithRandomAccess> | |
| WasmBinaryWriter::getRemovableIfUnusedHintsBuffer() { | |
| WRITE_BOOLEAN_HINT(Annotations::RemovableIfUnusedHint, removableIfUnused); | |
| } | |
| std::optional<BufferWithRandomAccess> | |
| WasmBinaryWriter::getJSCalledHintsBuffer() { | |
| WRITE_BOOLEAN_HINT(Annotations::JSCalledHint, jsCalled); | |
| } | |
| std::optional<BufferWithRandomAccess> | |
| WasmBinaryWriter::getIdempotentHintsBuffer() { | |
| WRITE_BOOLEAN_HINT(Annotations::IdempotentHint, idempotent); | |
| } | |
| std::optional<BufferWithRandomAccess> | |
| WasmBinaryWriter::getToolchainInlineHintsBuffer() { | |
| WRITE_I7_HINT(Annotations::ToolchainInlineHint, toolchainInline); | |
| } | |
| std::optional<BufferWithRandomAccess> | |
| WasmBinaryWriter::getNoReturnHintsBuffer() { | |
| WRITE_BOOLEAN_HINT(Annotations::NoReturnHint, noReturn); | |
| } | |
| void WasmBinaryWriter::writeData(const char* data, size_t size) { | |
| for (size_t i = 0; i < size; i++) { | |
| o << int8_t(data[i]); | |
| } | |
| } | |
| void WasmBinaryWriter::writeInlineString(std::string_view name) { | |
| o.writeInlineString(name); | |
| } | |
| void WasmBinaryWriter::writeInlineBuffer(const char* data, size_t size) { | |
| o << U32LEB(size); | |
| writeData(data, size); | |
| } | |
| void WasmBinaryWriter::writeType(Type type) { | |
| type = type.asWrittenGivenFeatures(wasm->features); | |
| if (type.isRef()) { | |
| auto heapType = type.getHeapType(); | |
| if (type.isNullable() && heapType.isBasic() && !heapType.isShared()) { | |
| switch (heapType.getBasic(Unshared)) { | |
| case HeapType::ext: | |
| o << S32LEB(BinaryConsts::EncodedType::externref); | |
| return; | |
| case HeapType::any: | |
| o << S32LEB(BinaryConsts::EncodedType::anyref); | |
| return; | |
| case HeapType::func: | |
| o << S32LEB(BinaryConsts::EncodedType::funcref); | |
| return; | |
| case HeapType::cont: | |
| o << S32LEB(BinaryConsts::EncodedType::contref); | |
| return; | |
| case HeapType::eq: | |
| o << S32LEB(BinaryConsts::EncodedType::eqref); | |
| return; | |
| case HeapType::i31: | |
| o << S32LEB(BinaryConsts::EncodedType::i31ref); | |
| return; | |
| case HeapType::struct_: | |
| o << S32LEB(BinaryConsts::EncodedType::structref); | |
| return; | |
| case HeapType::array: | |
| o << S32LEB(BinaryConsts::EncodedType::arrayref); | |
| return; | |
| case HeapType::exn: | |
| o << S32LEB(BinaryConsts::EncodedType::exnref); | |
| return; | |
| case HeapType::string: | |
| o << S32LEB(BinaryConsts::EncodedType::stringref); | |
| return; | |
| case HeapType::none: | |
| o << S32LEB(BinaryConsts::EncodedType::nullref); | |
| return; | |
| case HeapType::noext: | |
| o << S32LEB(BinaryConsts::EncodedType::nullexternref); | |
| return; | |
| case HeapType::nofunc: | |
| o << S32LEB(BinaryConsts::EncodedType::nullfuncref); | |
| return; | |
| case HeapType::noexn: | |
| o << S32LEB(BinaryConsts::EncodedType::nullexnref); | |
| return; | |
| case HeapType::nocont: | |
| o << S32LEB(BinaryConsts::EncodedType::nullcontref); | |
| return; | |
| case HeapType::waitqueue: | |
| o << S32LEB(BinaryConsts::EncodedHeapType::waitqueue); | |
| return; | |
| case HeapType::nowaitqueue: | |
| o << S32LEB(BinaryConsts::EncodedHeapType::nowaitqueue); | |
| return; | |
| } | |
| } | |
| if (type.isNullable()) { | |
| o << S32LEB(BinaryConsts::EncodedType::nullable); | |
| } else { | |
| o << S32LEB(BinaryConsts::EncodedType::nonnullable); | |
| } | |
| writeHeapType(type.getHeapType(), type.getExactness()); | |
| return; | |
| } | |
| int ret = 0; | |
| TODO_SINGLE_COMPOUND(type); | |
| switch (type.getBasic()) { | |
| // None only used for block signatures. TODO: Separate out? | |
| case Type::none: | |
| ret = BinaryConsts::EncodedType::Empty; | |
| break; | |
| case Type::i32: | |
| ret = BinaryConsts::EncodedType::i32; | |
| break; | |
| case Type::i64: | |
| ret = BinaryConsts::EncodedType::i64; | |
| break; | |
| case Type::f32: | |
| ret = BinaryConsts::EncodedType::f32; | |
| break; | |
| case Type::f64: | |
| ret = BinaryConsts::EncodedType::f64; | |
| break; | |
| case Type::v128: | |
| ret = BinaryConsts::EncodedType::v128; | |
| break; | |
| default: | |
| WASM_UNREACHABLE("unexpected type"); | |
| } | |
| o << S32LEB(ret); | |
| } | |
| void WasmBinaryWriter::writeHeapType(HeapType type, Exactness exactness) { | |
| type = type.asWrittenGivenFeatures(wasm->features); | |
| if (!wasm->features.hasCustomDescriptors()) { | |
| exactness = Inexact; | |
| } | |
| assert(!type.isBasic() || exactness == Inexact); | |
| if (exactness == Exact) { | |
| o << uint8_t(BinaryConsts::EncodedType::Exact); | |
| } | |
| if (!type.isBasic()) { | |
| o << S64LEB(getTypeIndex(type)); // TODO: Actually s33 | |
| return; | |
| } | |
| int ret = 0; | |
| if (type.isShared()) { | |
| o << uint8_t(BinaryConsts::EncodedType::Shared); | |
| } | |
| switch (type.getBasic(Unshared)) { | |
| case HeapType::ext: | |
| ret = BinaryConsts::EncodedHeapType::ext; | |
| break; | |
| case HeapType::func: | |
| ret = BinaryConsts::EncodedHeapType::func; | |
| break; | |
| case HeapType::cont: | |
| ret = BinaryConsts::EncodedHeapType::cont; | |
| break; | |
| case HeapType::any: | |
| ret = BinaryConsts::EncodedHeapType::any; | |
| break; | |
| case HeapType::eq: | |
| ret = BinaryConsts::EncodedHeapType::eq; | |
| break; | |
| case HeapType::i31: | |
| ret = BinaryConsts::EncodedHeapType::i31; | |
| break; | |
| case HeapType::struct_: | |
| ret = BinaryConsts::EncodedHeapType::struct_; | |
| break; | |
| case HeapType::array: | |
| ret = BinaryConsts::EncodedHeapType::array; | |
| break; | |
| case HeapType::exn: | |
| ret = BinaryConsts::EncodedHeapType::exn; | |
| break; | |
| case HeapType::string: | |
| ret = BinaryConsts::EncodedHeapType::string; | |
| break; | |
| case HeapType::none: | |
| ret = BinaryConsts::EncodedHeapType::none; | |
| break; | |
| case HeapType::noext: | |
| ret = BinaryConsts::EncodedHeapType::noext; | |
| break; | |
| case HeapType::nofunc: | |
| ret = BinaryConsts::EncodedHeapType::nofunc; | |
| break; | |
| case HeapType::noexn: | |
| ret = BinaryConsts::EncodedHeapType::noexn; | |
| break; | |
| case HeapType::nocont: | |
| ret = BinaryConsts::EncodedHeapType::nocont; | |
| break; | |
| case HeapType::waitqueue: | |
| ret = BinaryConsts::EncodedHeapType::waitqueue; | |
| break; | |
| case HeapType::nowaitqueue: | |
| ret = BinaryConsts::EncodedHeapType::nowaitqueue; | |
| break; | |
| } | |
| o << S64LEB(ret); // TODO: Actually s33 | |
| } | |
| void WasmBinaryWriter::writeIndexedHeapType(HeapType type) { | |
| o << U32LEB(getTypeIndex(type)); | |
| } | |
| void WasmBinaryWriter::writeField(const Field& field) { | |
| if (field.type == Type::i32 && field.packedType != Field::NotPacked) { | |
| if (field.packedType == Field::i8) { | |
| o << S32LEB(BinaryConsts::EncodedType::i8); | |
| } else if (field.packedType == Field::i16) { | |
| o << S32LEB(BinaryConsts::EncodedType::i16); | |
| } else { | |
| WASM_UNREACHABLE("invalid packed type"); | |
| } | |
| } else { | |
| writeType(field.type); | |
| } | |
| o << U32LEB(field.mutable_); | |
| } | |
| void WasmBinaryWriter::writeMemoryOrder(MemoryOrder order, bool isRMW) { | |
| uint8_t code = 0; | |
| switch (order) { | |
| case MemoryOrder::Unordered: | |
| // Non-atomic get or set does not need a memory order. | |
| return; | |
| case MemoryOrder::SeqCst: | |
| code = BinaryConsts::OrderSeqCst; | |
| break; | |
| case MemoryOrder::AcqRel: | |
| code = BinaryConsts::OrderAcqRel; | |
| break; | |
| case MemoryOrder::Relaxed: | |
| code = BinaryConsts::OrderRelaxed; | |
| break; | |
| } | |
| if (isRMW) { | |
| o << uint8_t((code << 4) | code); | |
| } else { | |
| o << code; | |
| } | |
| } | |
| // reader | |
| WasmBinaryReader::WasmBinaryReader(Module& wasm, | |
| FeatureSet features, | |
| const std::vector<char>& input, | |
| std::vector<char>& sourceMap) | |
| : wasm(wasm), allocator(wasm.allocator), input(input), builder(wasm), | |
| sourceMapReader(sourceMap) { | |
| wasm.features = features; | |
| } | |
| void WasmBinaryReader::preScan() { | |
| assert(pos == 0); | |
| getInt32(); // magic | |
| getInt32(); // version | |
| bool foundDWARF = false; | |
| while (more()) { | |
| uint8_t sectionCode = getInt8(); | |
| uint32_t payloadLen = getU32LEB(); | |
| if (uint64_t(pos) + uint64_t(payloadLen) > input.size()) { | |
| throwError("Section extends beyond end of input"); | |
| } | |
| auto oldPos = pos; | |
| if (sectionCode == BinaryConsts::Section::Custom) { | |
| auto sectionName = getInlineString(); | |
| if (sectionName == Annotations::BranchHint || | |
| sectionName == Annotations::InlineHint || | |
| sectionName == Annotations::RemovableIfUnusedHint || | |
| sectionName == Annotations::JSCalledHint || | |
| sectionName == Annotations::IdempotentHint || | |
| sectionName == Annotations::ToolchainInlineHint || | |
| sectionName == Annotations::NoReturnHint) { | |
| // Code annotations require code locations. | |
| // TODO: We could note which functions require code locations, as an | |
| // optimization. | |
| needCodeLocations = true; | |
| } else if (DWARF && Debug::isDWARFSection(sectionName)) { | |
| // DWARF sections contain code offsets. | |
| needCodeLocations = true; | |
| foundDWARF = true; | |
| } else if (debugInfo && | |
| sectionName == BinaryConsts::CustomSections::Name) { | |
| readNames(oldPos, payloadLen); | |
| } else if (sectionName == BinaryConsts::CustomSections::TargetFeatures) { | |
| readFeatures(oldPos, payloadLen); | |
| } | |
| // TODO: We could stop early in some cases, if we've seen enough (e.g. | |
| // seeing Code implies no BranchHint will appear, due to ordering). | |
| } | |
| pos = oldPos + payloadLen; | |
| } | |
| if (DWARF && !foundDWARF) { | |
| // The user asked for DWARF, but no DWARF sections exist in practice, so | |
| // disable the support. | |
| DWARF = false; | |
| } | |
| // Reset. | |
| pos = 0; | |
| } | |
| void WasmBinaryReader::read() { | |
| preScan(); | |
| readHeader(); | |
| sourceMapReader.parse(wasm); | |
| // Read sections until the end | |
| while (more()) { | |
| uint8_t sectionCode = getInt8(); | |
| uint32_t payloadLen = getU32LEB(); | |
| if (uint64_t(pos) + uint64_t(payloadLen) > input.size()) { | |
| throwError("Section extends beyond end of input"); | |
| } | |
| auto oldPos = pos; | |
| // Note the section in the list of seen sections, as almost no sections can | |
| // appear more than once, and verify those that shouldn't do not. | |
| if (sectionCode != BinaryConsts::Section::Custom && | |
| !seenSections.insert(sectionCode).second) { | |
| throwError("section seen more than once: " + std::to_string(sectionCode)); | |
| } | |
| switch (sectionCode) { | |
| case BinaryConsts::Section::Start: | |
| readStart(); | |
| break; | |
| case BinaryConsts::Section::Memory: | |
| readMemories(); | |
| break; | |
| case BinaryConsts::Section::Type: | |
| readTypes(); | |
| break; | |
| case BinaryConsts::Section::Import: | |
| readImports(); | |
| break; | |
| case BinaryConsts::Section::Function: | |
| readFunctionSignatures(); | |
| break; | |
| case BinaryConsts::Section::Code: | |
| if (needCodeLocations) { | |
| codeSectionLocation = pos; | |
| } | |
| readFunctions(); | |
| break; | |
| case BinaryConsts::Section::Export: | |
| readExports(); | |
| break; | |
| case BinaryConsts::Section::Element: | |
| readElementSegments(); | |
| break; | |
| case BinaryConsts::Section::Strings: | |
| readStrings(); | |
| break; | |
| case BinaryConsts::Section::Global: | |
| readGlobals(); | |
| break; | |
| case BinaryConsts::Section::Data: | |
| readDataSegments(); | |
| break; | |
| case BinaryConsts::Section::DataCount: | |
| readDataSegmentCount(); | |
| break; | |
| case BinaryConsts::Section::Table: | |
| readTableDeclarations(); | |
| break; | |
| case BinaryConsts::Section::Tag: | |
| readTags(); | |
| break; | |
| case BinaryConsts::Section::Custom: { | |
| readCustomSection(payloadLen); | |
| if (pos > oldPos + payloadLen) { | |
| throwError("bad user section size, started at " + | |
| std::to_string(oldPos) + " plus payload " + | |
| std::to_string(payloadLen) + | |
| " not being equal to new position " + std::to_string(pos)); | |
| } | |
| pos = oldPos + payloadLen; | |
| break; | |
| } | |
| default: | |
| throwError(std::string("unrecognized section ID: ") + | |
| std::to_string(sectionCode)); | |
| } | |
| // make sure we advanced exactly past this section | |
| if (pos != oldPos + payloadLen) { | |
| throwError("bad section size, started at " + std::to_string(oldPos) + | |
| " plus payload " + std::to_string(payloadLen) + | |
| " not being equal to new position " + std::to_string(pos)); | |
| } | |
| } | |
| // Go back and parse annotations we deferred. | |
| for (auto& [annotationPos, read] : deferredAnnotationSections) { | |
| // Rewind to the right position, and read. | |
| pos = annotationPos; | |
| read(); | |
| } | |
| validateBinary(); | |
| } | |
| void WasmBinaryReader::readCustomSection(size_t payloadLen) { | |
| auto oldPos = pos; | |
| Name sectionName = getInlineString(); | |
| size_t read = pos - oldPos; | |
| if (read > payloadLen) { | |
| throwError("bad user section size"); | |
| } | |
| payloadLen -= read; | |
| if (sectionName.equals(BinaryConsts::CustomSections::Name) || | |
| sectionName.equals(BinaryConsts::CustomSections::TargetFeatures)) { | |
| // We already read the name and target features sections before anything | |
| // else. | |
| pos += payloadLen; | |
| } else if (sectionName.equals(BinaryConsts::CustomSections::Dylink)) { | |
| readDylink(payloadLen); | |
| } else if (sectionName.equals(BinaryConsts::CustomSections::Dylink0)) { | |
| readDylink0(payloadLen); | |
| } else if (sectionName == Annotations::BranchHint) { | |
| // Deferred. | |
| deferredAnnotationSections.push_back(AnnotationSectionInfo{ | |
| pos, [this, payloadLen]() { this->readBranchHints(payloadLen); }}); | |
| } else if (sectionName == Annotations::InlineHint) { | |
| deferredAnnotationSections.push_back(AnnotationSectionInfo{ | |
| pos, [this, payloadLen]() { this->readInlineHints(payloadLen); }}); | |
| } else if (sectionName == Annotations::RemovableIfUnusedHint) { | |
| deferredAnnotationSections.push_back( | |
| AnnotationSectionInfo{pos, [this, payloadLen]() { | |
| this->readRemovableIfUnusedHints(payloadLen); | |
| }}); | |
| } else if (sectionName == Annotations::JSCalledHint) { | |
| deferredAnnotationSections.push_back(AnnotationSectionInfo{ | |
| pos, [this, payloadLen]() { this->readJSCalledHints(payloadLen); }}); | |
| } else if (sectionName == Annotations::IdempotentHint) { | |
| deferredAnnotationSections.push_back(AnnotationSectionInfo{ | |
| pos, [this, payloadLen]() { this->readIdempotentHints(payloadLen); }}); | |
| } else if (sectionName == Annotations::ToolchainInlineHint) { | |
| deferredAnnotationSections.push_back( | |
| AnnotationSectionInfo{pos, [this, payloadLen]() { | |
| this->readToolchainInlineHints(payloadLen); | |
| }}); | |
| } else if (sectionName == Annotations::NoReturnHint) { | |
| deferredAnnotationSections.push_back(AnnotationSectionInfo{ | |
| pos, [this, payloadLen]() { this->readNoReturnHints(payloadLen); }}); | |
| } else { | |
| // an unfamiliar custom section | |
| if (sectionName.equals(BinaryConsts::CustomSections::Linking)) { | |
| std::cerr | |
| << "warning: linking section is present, so this is not a standard " | |
| "wasm file - binaryen cannot handle this properly!\n"; | |
| } | |
| wasm.customSections.resize(wasm.customSections.size() + 1); | |
| auto& section = wasm.customSections.back(); | |
| section.name = sectionName.view(); | |
| auto data = getByteView(payloadLen); | |
| section.data = {data.begin(), data.end()}; | |
| } | |
| } | |
| std::string_view WasmBinaryReader::getByteView(size_t size) { | |
| if (size > input.size() || pos > input.size() - size) { | |
| throwError("unexpected end of input"); | |
| } | |
| pos += size; | |
| return {input.data() + (pos - size), size}; | |
| } | |
| uint8_t WasmBinaryReader::peekInt8() { | |
| if (!more()) { | |
| throwError("unexpected end of input"); | |
| } | |
| return input[pos]; | |
| } | |
| uint8_t WasmBinaryReader::getInt8() { | |
| if (!more()) { | |
| throwError("unexpected end of input"); | |
| } | |
| return input[pos++]; | |
| } | |
| uint16_t WasmBinaryReader::getInt16() { | |
| auto ret = uint16_t(getInt8()); | |
| ret |= uint16_t(getInt8()) << 8; | |
| return ret; | |
| } | |
| uint32_t WasmBinaryReader::getInt32() { | |
| auto ret = uint32_t(getInt16()); | |
| ret |= uint32_t(getInt16()) << 16; | |
| return ret; | |
| } | |
| uint64_t WasmBinaryReader::getInt64() { | |
| auto ret = uint64_t(getInt32()); | |
| ret |= uint64_t(getInt32()) << 32; | |
| return ret; | |
| } | |
| uint8_t WasmBinaryReader::getLaneIndex(size_t lanes) { | |
| auto ret = getInt8(); | |
| if (ret >= lanes) { | |
| throwError("Illegal lane index"); | |
| } | |
| return ret; | |
| } | |
| Literal WasmBinaryReader::getFloat32Literal() { | |
| auto ret = Literal(getInt32()); | |
| ret = ret.castToF32(); | |
| return ret; | |
| } | |
| Literal WasmBinaryReader::getFloat64Literal() { | |
| auto ret = Literal(getInt64()); | |
| ret = ret.castToF64(); | |
| return ret; | |
| } | |
| Literal WasmBinaryReader::getVec128Literal() { | |
| std::array<uint8_t, 16> bytes; | |
| for (auto i = 0; i < 16; ++i) { | |
| bytes[i] = getInt8(); | |
| } | |
| auto ret = Literal(bytes.data()); | |
| return ret; | |
| } | |
| uint32_t WasmBinaryReader::getU32LEB() { | |
| U32LEB ret; | |
| ret.read([&]() { return getInt8(); }); | |
| return ret.value; | |
| } | |
| uint64_t WasmBinaryReader::getU64LEB() { | |
| U64LEB ret; | |
| ret.read([&]() { return getInt8(); }); | |
| return ret.value; | |
| } | |
| int32_t WasmBinaryReader::getS32LEB() { | |
| S32LEB ret; | |
| ret.read([&]() { return (int8_t)getInt8(); }); | |
| return ret.value; | |
| } | |
| int64_t WasmBinaryReader::getS64LEB() { | |
| S64LEB ret; | |
| ret.read([&]() { return (int8_t)getInt8(); }); | |
| return ret.value; | |
| } | |
| bool WasmBinaryReader::getBasicType(int32_t code, Type& out) { | |
| switch (code) { | |
| case BinaryConsts::EncodedType::i32: | |
| out = Type::i32; | |
| return true; | |
| case BinaryConsts::EncodedType::i64: | |
| out = Type::i64; | |
| return true; | |
| case BinaryConsts::EncodedType::f32: | |
| out = Type::f32; | |
| return true; | |
| case BinaryConsts::EncodedType::f64: | |
| out = Type::f64; | |
| return true; | |
| case BinaryConsts::EncodedType::v128: | |
| out = Type::v128; | |
| return true; | |
| case BinaryConsts::EncodedType::funcref: | |
| out = Type(HeapType::func, Nullable); | |
| return true; | |
| case BinaryConsts::EncodedType::contref: | |
| out = Type(HeapType::cont, Nullable); | |
| return true; | |
| case BinaryConsts::EncodedType::externref: | |
| out = Type(HeapType::ext, Nullable); | |
| return true; | |
| case BinaryConsts::EncodedType::anyref: | |
| out = Type(HeapType::any, Nullable); | |
| return true; | |
| case BinaryConsts::EncodedType::eqref: | |
| out = Type(HeapType::eq, Nullable); | |
| return true; | |
| case BinaryConsts::EncodedType::i31ref: | |
| out = Type(HeapType::i31, Nullable); | |
| return true; | |
| case BinaryConsts::EncodedType::structref: | |
| out = Type(HeapType::struct_, Nullable); | |
| return true; | |
| case BinaryConsts::EncodedType::arrayref: | |
| out = Type(HeapType::array, Nullable); | |
| return true; | |
| case BinaryConsts::EncodedType::exnref: | |
| out = Type(HeapType::exn, Nullable); | |
| return true; | |
| case BinaryConsts::EncodedType::stringref: | |
| out = Type(HeapType::string, Nullable); | |
| return true; | |
| case BinaryConsts::EncodedType::nullref: | |
| out = Type(HeapType::none, Nullable); | |
| return true; | |
| case BinaryConsts::EncodedType::nullexternref: | |
| out = Type(HeapType::noext, Nullable); | |
| return true; | |
| case BinaryConsts::EncodedType::nullfuncref: | |
| out = Type(HeapType::nofunc, Nullable); | |
| return true; | |
| case BinaryConsts::EncodedType::nullexnref: | |
| out = Type(HeapType::noexn, Nullable); | |
| return true; | |
| case BinaryConsts::EncodedType::nullcontref: | |
| out = Type(HeapType::nocont, Nullable); | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| bool WasmBinaryReader::getBasicHeapType(int64_t code, HeapType& out) { | |
| switch (code) { | |
| case BinaryConsts::EncodedHeapType::func: | |
| out = HeapType::func; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::cont: | |
| out = HeapType::cont; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::ext: | |
| out = HeapType::ext; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::any: | |
| out = HeapType::any; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::eq: | |
| out = HeapType::eq; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::i31: | |
| out = HeapType::i31; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::struct_: | |
| out = HeapType::struct_; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::array: | |
| out = HeapType::array; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::exn: | |
| out = HeapType::exn; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::string: | |
| out = HeapType::string; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::none: | |
| out = HeapType::none; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::noext: | |
| out = HeapType::noext; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::nofunc: | |
| out = HeapType::nofunc; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::noexn: | |
| out = HeapType::noexn; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::nocont: | |
| out = HeapType::nocont; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::waitqueue: | |
| out = HeapType::waitqueue; | |
| return true; | |
| case BinaryConsts::EncodedHeapType::nowaitqueue: | |
| out = HeapType::nowaitqueue; | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| Signature WasmBinaryReader::getBlockType() { | |
| // Single value types are negative; signature indices are non-negative. | |
| auto code = getS32LEB(); | |
| if (code >= 0) { | |
| return getSignatureByTypeIndex(code); | |
| } | |
| if (code == BinaryConsts::EncodedType::Empty) { | |
| return Signature(); | |
| } | |
| return Signature(Type::none, getType(code)); | |
| } | |
| Type WasmBinaryReader::getType(int code) { | |
| Type type; | |
| if (getBasicType(code, type)) { | |
| return type; | |
| } | |
| auto [heapType, exactness] = getHeapType(); | |
| switch (code) { | |
| case BinaryConsts::EncodedType::nullable: | |
| return Type(heapType, Nullable, exactness); | |
| case BinaryConsts::EncodedType::nonnullable: | |
| return Type(heapType, NonNullable, exactness); | |
| default: | |
| throwError("invalid wasm type: " + std::to_string(code)); | |
| } | |
| WASM_UNREACHABLE("unexpected type"); | |
| } | |
| Type WasmBinaryReader::getType() { return getType(getS32LEB()); } | |
| std::pair<HeapType, Exactness> WasmBinaryReader::getHeapType() { | |
| auto type = getS64LEB(); // TODO: Actually s33 | |
| auto exactness = Inexact; | |
| if (type == BinaryConsts::EncodedType::ExactLEB) { | |
| exactness = Exact; | |
| type = getS64LEB(); // TODO: Actually s33 | |
| } | |
| // Single heap types are negative; heap type indices are non-negative | |
| if (type >= 0) { | |
| if (size_t(type) >= types.size()) { | |
| throwError("invalid type index: " + std::to_string(type)); | |
| } | |
| return {types[type], exactness}; | |
| } | |
| if (exactness == Exact) { | |
| throwError("invalid type index: " + std::to_string(type)); | |
| } | |
| auto share = Unshared; | |
| if (type == BinaryConsts::EncodedType::SharedLEB) { | |
| share = Shared; | |
| type = getS64LEB(); // TODO: Actually s33 | |
| } | |
| HeapType ht; | |
| if (getBasicHeapType(type, ht)) { | |
| return {ht.getBasic(share), Inexact}; | |
| } | |
| throwError("invalid wasm heap type: " + std::to_string(type)); | |
| } | |
| HeapType WasmBinaryReader::getIndexedHeapType() { | |
| auto index = getU32LEB(); | |
| if (index >= types.size()) { | |
| throwError("invalid heap type index: " + std::to_string(index)); | |
| } | |
| return types[index]; | |
| } | |
| Type WasmBinaryReader::getConcreteType() { | |
| auto type = getType(); | |
| if (!type.isConcrete()) { | |
| throwError("non-concrete type when one expected"); | |
| } | |
| return type; | |
| } | |
| Name WasmBinaryReader::getInlineString(bool requireValid) { | |
| auto len = getU32LEB(); | |
| auto data = getByteView(len); | |
| if (requireValid && !String::isUTF8(data)) { | |
| throwError("invalid UTF-8 string"); | |
| } | |
| return Name(data); | |
| } | |
| void WasmBinaryReader::verifyInt8(int8_t x) { | |
| int8_t y = getInt8(); | |
| if (x != y) { | |
| throwError("surprising value"); | |
| } | |
| } | |
| void WasmBinaryReader::verifyInt16(int16_t x) { | |
| int16_t y = getInt16(); | |
| if (x != y) { | |
| throwError("surprising value"); | |
| } | |
| } | |
| void WasmBinaryReader::verifyInt32(int32_t x) { | |
| int32_t y = getInt32(); | |
| if (x != y) { | |
| throwError("surprising value"); | |
| } | |
| } | |
| void WasmBinaryReader::verifyInt64(int64_t x) { | |
| int64_t y = getInt64(); | |
| if (x != y) { | |
| throwError("surprising value"); | |
| } | |
| } | |
| void WasmBinaryReader::readHeader() { | |
| verifyInt32(BinaryConsts::Magic); | |
| auto version = getInt32(); | |
| if (version != BinaryConsts::Version) { | |
| if (version == 0x1000d) { | |
| throwError("this looks like a wasm component, which Binaryen does not " | |
| "support yet (see " | |
| "https://github.com/WebAssembly/binaryen/issues/6728)"); | |
| } | |
| throwError("invalid version"); | |
| } | |
| } | |
| void WasmBinaryReader::readStart() { | |
| startIndex = getU32LEB(); | |
| wasm.start = getFunctionName(startIndex); | |
| } | |
| static Name makeName(std::string prefix, size_t counter) { | |
| return Name(prefix + std::to_string(counter)); | |
| } | |
| // Look up a name from the names section or use a validated version of the | |
| // provided name. Return the name and whether it is explicit in the input. | |
| static std::pair<Name, bool> | |
| getOrMakeName(const std::unordered_map<Index, Name>& nameMap, | |
| Index i, | |
| Name name, | |
| std::unordered_set<Name>& usedNames) { | |
| if (auto it = nameMap.find(i); it != nameMap.end()) { | |
| return {it->second, true}; | |
| } else { | |
| auto valid = Names::getValidNameGivenExisting(name, usedNames); | |
| usedNames.insert(valid); | |
| return {valid, false}; | |
| } | |
| } | |
| void WasmBinaryReader::readMemories() { | |
| auto num = getU32LEB(); | |
| auto numImports = wasm.memories.size(); | |
| for (auto& [index, name] : memoryNames) { | |
| if (index >= num + numImports) { | |
| std::cerr << "warning: memory index out of bounds in name section: " | |
| << name << " at index " << index << '\n'; | |
| } | |
| } | |
| for (size_t i = 0; i < num; i++) { | |
| auto [name, isExplicit] = getOrMakeName( | |
| memoryNames, numImports + i, makeName("", i), usedMemoryNames); | |
| auto memory = Builder::makeMemory(name); | |
| memory->hasExplicitName = isExplicit; | |
| getResizableLimits(memory->initial, | |
| memory->max, | |
| memory->shared, | |
| memory->addressType, | |
| memory->pageSizeLog2, | |
| Memory::kUnlimitedSize); | |
| wasm.addMemory(std::move(memory)); | |
| } | |
| } | |
| void WasmBinaryReader::readTypes() { | |
| TypeBuilder builder(getU32LEB(), wasm.features); | |
| auto readHeapType = [&]() -> std::pair<HeapType, Exactness> { | |
| int64_t htCode = getS64LEB(); // TODO: Actually s33 | |
| auto exactness = Inexact; | |
| if (htCode == BinaryConsts::EncodedType::ExactLEB) { | |
| exactness = Exact; | |
| htCode = getS64LEB(); // TODO: Actually s33 | |
| } | |
| if (htCode >= 0) { | |
| if (size_t(htCode) >= builder.size()) { | |
| throwError("invalid type index: " + std::to_string(htCode)); | |
| } | |
| return {builder.getTempHeapType(size_t(htCode)), exactness}; | |
| } | |
| if (exactness == Exact) { | |
| throwError("invalid type index: " + std::to_string(htCode)); | |
| } | |
| auto share = Unshared; | |
| if (htCode == BinaryConsts::EncodedType::SharedLEB) { | |
| share = Shared; | |
| htCode = getS64LEB(); // TODO: Actually s33 | |
| } | |
| HeapType ht; | |
| if (getBasicHeapType(htCode, ht)) { | |
| return {ht.getBasic(share), Inexact}; | |
| } | |
| throwError("invalid wasm heap type: " + std::to_string(htCode)); | |
| }; | |
| auto makeType = [&](int32_t typeCode) { | |
| Type type; | |
| if (getBasicType(typeCode, type)) { | |
| return type; | |
| } | |
| switch (typeCode) { | |
| case BinaryConsts::EncodedType::nullable: | |
| case BinaryConsts::EncodedType::nonnullable: { | |
| auto nullability = typeCode == BinaryConsts::EncodedType::nullable | |
| ? Nullable | |
| : NonNullable; | |
| auto [ht, exactness] = readHeapType(); | |
| if (ht.isBasic()) { | |
| return Type(ht, nullability, exactness); | |
| } | |
| return builder.getTempRefType(ht, nullability, exactness); | |
| } | |
| default: | |
| throwError("unexpected type index: " + std::to_string(typeCode)); | |
| } | |
| WASM_UNREACHABLE("unexpected type"); | |
| }; | |
| auto readType = [&]() { return makeType(getS32LEB()); }; | |
| auto readSignatureDef = [&]() { | |
| std::vector<Type> params; | |
| std::vector<Type> results; | |
| size_t numParams = getU32LEB(); | |
| for (size_t j = 0; j < numParams; j++) { | |
| params.push_back(readType()); | |
| } | |
| auto numResults = getU32LEB(); | |
| for (size_t j = 0; j < numResults; j++) { | |
| results.push_back(readType()); | |
| } | |
| return Signature(builder.getTempTupleType(params), | |
| builder.getTempTupleType(results)); | |
| }; | |
| auto readContinuationDef = [&]() { | |
| auto [ht, exactness] = readHeapType(); | |
| if (exactness != Inexact) { | |
| throw ParseException("invalid exact type in cont definition"); | |
| } | |
| if (!ht.isSignature()) { | |
| throw ParseException("cont types must be built from function types"); | |
| } | |
| return Continuation(ht); | |
| }; | |
| auto readMutability = [&]() { | |
| switch (getU32LEB()) { | |
| case 0: | |
| return Immutable; | |
| case 1: | |
| return Mutable; | |
| default: | |
| throw ParseException("Expected 0 or 1 for mutability"); | |
| } | |
| }; | |
| auto readFieldDef = [&]() { | |
| // The value may be a general wasm type, or one of the types only possible | |
| // in a field. | |
| auto typeCode = getS32LEB(); | |
| if (typeCode == BinaryConsts::EncodedType::i8) { | |
| auto mutable_ = readMutability(); | |
| return Field(Field::i8, mutable_); | |
| } | |
| if (typeCode == BinaryConsts::EncodedType::i16) { | |
| auto mutable_ = readMutability(); | |
| return Field(Field::i16, mutable_); | |
| } | |
| // It's a regular wasm value. | |
| auto type = makeType(typeCode); | |
| auto mutable_ = readMutability(); | |
| return Field(type, mutable_); | |
| }; | |
| auto readStructDef = [&]() { | |
| FieldList fields; | |
| size_t numFields = getU32LEB(); | |
| for (size_t j = 0; j < numFields; j++) { | |
| fields.push_back(readFieldDef()); | |
| } | |
| return Struct(std::move(fields)); | |
| }; | |
| for (size_t i = 0; i < builder.size(); i++) { | |
| auto form = getInt8(); | |
| if (form == BinaryConsts::EncodedType::Rec) { | |
| uint32_t groupSize = getU32LEB(); | |
| if (groupSize == 0u) { | |
| // TODO: Support groups of size zero by shrinking the builder. | |
| throwError("Recursion groups of size zero not supported"); | |
| } | |
| // The group counts as one element in the type section, so we have to | |
| // allocate space for the extra types. | |
| builder.grow(groupSize - 1); | |
| builder.createRecGroup(i, groupSize); | |
| form = getInt8(); | |
| } | |
| if (form == BinaryConsts::EncodedType::Sub || | |
| form == BinaryConsts::EncodedType::SubFinal) { | |
| if (form == BinaryConsts::EncodedType::Sub) { | |
| builder[i].setOpen(); | |
| } | |
| uint32_t supers = getU32LEB(); | |
| if (supers > 0) { | |
| if (supers != 1) { | |
| throwError("Invalid type definition with " + std::to_string(supers) + | |
| " supertypes"); | |
| } | |
| auto superIdx = getU32LEB(); | |
| if (superIdx >= builder.size()) { | |
| throwError("invalid supertype index: " + std::to_string(superIdx)); | |
| } | |
| builder[i].subTypeOf(builder[superIdx]); | |
| } | |
| form = getInt8(); | |
| } | |
| if (form == BinaryConsts::EncodedType::Shared) { | |
| builder[i].setShared(); | |
| form = getInt8(); | |
| } | |
| if (form == BinaryConsts::EncodedType::Describes) { | |
| auto descIdx = getU32LEB(); | |
| if (descIdx >= builder.size()) { | |
| throwError("invalid described type index: " + std::to_string(descIdx)); | |
| } | |
| builder[i].describes(builder[descIdx]); | |
| form = getInt8(); | |
| } | |
| if (form == BinaryConsts::EncodedType::Descriptor) { | |
| auto descIdx = getU32LEB(); | |
| if (descIdx >= builder.size()) { | |
| throwError("invalid descriptor type index: " + std::to_string(descIdx)); | |
| } | |
| builder[i].descriptor(builder[descIdx]); | |
| form = getInt8(); | |
| } | |
| if (form == BinaryConsts::EncodedType::Func) { | |
| builder[i] = readSignatureDef(); | |
| } else if (form == BinaryConsts::EncodedType::Cont) { | |
| builder[i] = readContinuationDef(); | |
| } else if (form == BinaryConsts::EncodedType::Struct) { | |
| builder[i] = readStructDef(); | |
| } else if (form == BinaryConsts::EncodedType::Array) { | |
| builder[i] = Array(readFieldDef()); | |
| } else { | |
| throwError("Bad type form " + std::to_string(form)); | |
| } | |
| } | |
| auto result = builder.build(); | |
| if (auto* err = result.getError()) { | |
| Fatal() << "invalid type: " << err->reason << " at index " << err->index; | |
| } | |
| types = std::move(*result); | |
| // Record the type indices. | |
| for (Index i = 0; i < types.size(); ++i) { | |
| wasm.typeIndices.insert({types[i], i}); | |
| } | |
| // Assign names from the names section. | |
| for (auto& [index, name] : typeNames) { | |
| if (index >= types.size()) { | |
| std::cerr << "warning: type index out of bounds in name section: " << name | |
| << " at index " << index << '\n'; | |
| continue; | |
| } | |
| wasm.typeNames[types[index]].name = name; | |
| } | |
| for (auto& [index, fields] : fieldNames) { | |
| if (index >= types.size()) { | |
| std::cerr | |
| << "warning: type index out of bounds in name section: fields at index " | |
| << index << '\n'; | |
| continue; | |
| } | |
| if (!types[index].isStruct()) { | |
| std::cerr << "warning: field names applied to non-struct type at index " | |
| << index << '\n'; | |
| continue; | |
| } | |
| auto& names = wasm.typeNames[types[index]].fieldNames; | |
| for (auto& [field, name] : fields) { | |
| if (field >= types[index].getStruct().fields.size()) { | |
| std::cerr << "warning: field index out of bounds in name section: " | |
| << name << " at index " << field << " in type " << index | |
| << '\n'; | |
| continue; | |
| } | |
| names[field] = name; | |
| } | |
| } | |
| } | |
| Name WasmBinaryReader::getFunctionName(Index index) { | |
| if (index >= wasm.functions.size()) { | |
| throwError("invalid function index"); | |
| } | |
| return wasm.functions[index]->name; | |
| } | |
| Name WasmBinaryReader::getTableName(Index index) { | |
| if (index >= wasm.tables.size()) { | |
| throwError("invalid table index"); | |
| } | |
| return wasm.tables[index]->name; | |
| } | |
| Name WasmBinaryReader::getMemoryName(Index index) { | |
| if (index >= wasm.memories.size()) { | |
| throwError("invalid memory index"); | |
| } | |
| return wasm.memories[index]->name; | |
| } | |
| Name WasmBinaryReader::getGlobalName(Index index) { | |
| if (index >= wasm.globals.size()) { | |
| throwError("invalid global index"); | |
| } | |
| return wasm.globals[index]->name; | |
| } | |
| Table* WasmBinaryReader::getTable(Index index) { | |
| if (index < wasm.tables.size()) { | |
| return wasm.tables[index].get(); | |
| } | |
| throwError("Table index out of range."); | |
| } | |
| Name WasmBinaryReader::getTagName(Index index) { | |
| if (index >= wasm.tags.size()) { | |
| throwError("invalid tag index"); | |
| } | |
| return wasm.tags[index]->name; | |
| } | |
| Name WasmBinaryReader::getDataName(Index index) { | |
| if (index >= wasm.dataSegments.size()) { | |
| throwError("invalid data segment index"); | |
| } | |
| return wasm.dataSegments[index]->name; | |
| } | |
| Name WasmBinaryReader::getElemName(Index index) { | |
| if (index >= wasm.elementSegments.size()) { | |
| throwError("invalid element segment index"); | |
| } | |
| return wasm.elementSegments[index]->name; | |
| } | |
| Memory* WasmBinaryReader::getMemory(Index index) { | |
| if (index < wasm.memories.size()) { | |
| return wasm.memories[index].get(); | |
| } | |
| throwError("Memory index out of range."); | |
| } | |
| void WasmBinaryReader::getResizableLimits(Address& initial, | |
| Address& max, | |
| bool& shared, | |
| Type& addressType, | |
| uint8_t& pageSizeLog2, | |
| Address defaultIfNoMax) { | |
| auto flags = getU32LEB(); | |
| bool hasMax = (flags & BinaryConsts::HasMaximum) != 0; | |
| bool isShared = (flags & BinaryConsts::IsShared) != 0; | |
| bool is64 = (flags & BinaryConsts::Is64) != 0; | |
| bool hasCustomPageSize = (flags & BinaryConsts::HasCustomPageSize) != 0; | |
| initial = is64 ? getU64LEB() : getU32LEB(); | |
| if (isShared && !hasMax) { | |
| throwError("shared memory must have max size"); | |
| } | |
| shared = isShared; | |
| addressType = is64 ? Type::i64 : Type::i32; | |
| if (hasMax) { | |
| max = is64 ? getU64LEB() : getU32LEB(); | |
| } else { | |
| max = defaultIfNoMax; | |
| } | |
| if (hasCustomPageSize) { | |
| auto readPageSizeLog2 = getU32LEB(); | |
| if (readPageSizeLog2 != 0 && | |
| readPageSizeLog2 != Memory::kDefaultPageSizeLog2) { | |
| throwError("Memory page size is only allowed to be 1 or 64 KiB"); | |
| } | |
| pageSizeLog2 = (uint8_t)readPageSizeLog2; | |
| } | |
| } | |
| void WasmBinaryReader::addImport(std::unique_ptr<Function> func) { | |
| auto [name, isExplicit] = | |
| getOrMakeName(functionNames, | |
| wasm.functions.size(), | |
| makeName("fimport$", wasm.functions.size()), | |
| usedFunctionNames); | |
| func->name = name; | |
| func->hasExplicitName = isExplicit; | |
| functionTypes.push_back(func->type.getHeapType()); | |
| setLocalNames(*func, wasm.functions.size()); | |
| wasm.addFunction(std::move(func)); | |
| } | |
| void WasmBinaryReader::addImport(std::unique_ptr<Table> table) { | |
| auto [name, isExplicit] = | |
| getOrMakeName(tableNames, | |
| wasm.tables.size(), | |
| makeName("timport$", wasm.tables.size()), | |
| usedTableNames); | |
| table->name = name; | |
| table->hasExplicitName = isExplicit; | |
| wasm.addTable(std::move(table)); | |
| } | |
| void WasmBinaryReader::addImport(std::unique_ptr<Memory> memory) { | |
| auto [name, isExplicit] = | |
| getOrMakeName(memoryNames, | |
| wasm.memories.size(), | |
| makeName("mimport$", wasm.memories.size()), | |
| usedMemoryNames); | |
| memory->name = name; | |
| memory->hasExplicitName = isExplicit; | |
| wasm.addMemory(std::move(memory)); | |
| } | |
| void WasmBinaryReader::addImport(std::unique_ptr<Global> global) { | |
| auto [name, isExplicit] = | |
| getOrMakeName(globalNames, | |
| wasm.globals.size(), | |
| makeName("gimport$", wasm.globals.size()), | |
| usedGlobalNames); | |
| global->name = name; | |
| global->hasExplicitName = isExplicit; | |
| wasm.addGlobal(std::move(global)); | |
| } | |
| void WasmBinaryReader::addImport(std::unique_ptr<Tag> tag) { | |
| auto [name, isExplicit] = | |
| getOrMakeName(tagNames, | |
| wasm.tags.size(), | |
| makeName("eimport$", wasm.tags.size()), | |
| usedTagNames); | |
| tag->name = name; | |
| tag->hasExplicitName = isExplicit; | |
| wasm.addTag(std::move(tag)); | |
| } | |
| std::unique_ptr<Function> | |
| WasmBinaryReader::readFunctionImport(Name module, Name base, uint32_t kind) { | |
| Builder builder(wasm); | |
| auto index = getU32LEB(); | |
| auto type = getTypeByIndex(index); | |
| if (!type.isSignature()) { | |
| throwError(std::string("Imported function ") + module.toString() + '.' + | |
| base.toString() + | |
| "'s type must be a signature. Given: " + type.toString()); | |
| } | |
| auto exact = (kind & BinaryConsts::ExactImport) ? Exact : Inexact; | |
| auto curr = builder.makeFunction("", Type(type, NonNullable, exact), {}); | |
| curr->module = module; | |
| curr->base = base; | |
| return curr; | |
| } | |
| std::unique_ptr<Table> WasmBinaryReader::readTableImport(Name module, | |
| Name base) { | |
| Builder builder(wasm); | |
| auto table = builder.makeTable(""); | |
| table->module = module; | |
| table->base = base; | |
| table->type = getType(); | |
| bool is_shared; | |
| uint8_t page_size = 0xff; | |
| getResizableLimits(table->initial, | |
| table->max, | |
| is_shared, | |
| table->addressType, | |
| page_size, | |
| Table::kUnlimitedSize); | |
| if (is_shared) { | |
| throwError("Tables may not be shared"); | |
| } | |
| if (page_size != 0xff) { | |
| throwError("Tables may not have a custom page size"); | |
| } | |
| return table; | |
| } | |
| std::unique_ptr<Memory> WasmBinaryReader::readMemoryImport(Name module, | |
| Name base) { | |
| Builder builder(wasm); | |
| auto memory = builder.makeMemory(""); | |
| memory->module = module; | |
| memory->base = base; | |
| getResizableLimits(memory->initial, | |
| memory->max, | |
| memory->shared, | |
| memory->addressType, | |
| memory->pageSizeLog2, | |
| Memory::kUnlimitedSize); | |
| return memory; | |
| } | |
| std::unique_ptr<Global> WasmBinaryReader::readGlobalImport(Name module, | |
| Name base) { | |
| Builder builder(wasm); | |
| auto type = getConcreteType(); | |
| auto mutable_ = getU32LEB(); | |
| if (mutable_ & ~1) { | |
| throwError("Global mutability must be 0 or 1"); | |
| } | |
| auto curr = builder.makeGlobal( | |
| "", type, nullptr, mutable_ ? Builder::Mutable : Builder::Immutable); | |
| curr->module = module; | |
| curr->base = base; | |
| return curr; | |
| } | |
| std::unique_ptr<Tag> WasmBinaryReader::readTagImport(Name module, Name base) { | |
| Builder builder(wasm); | |
| getInt8(); // Reserved 'attribute' field | |
| auto index = getU32LEB(); | |
| auto curr = builder.makeTag("", getSignatureByTypeIndex(index)); | |
| curr->module = module; | |
| curr->base = base; | |
| return curr; | |
| } | |
| template<typename T, typename ReadFunc> | |
| void WasmBinaryReader::readCompactImportsShared(Name module, | |
| ReadFunc readBaseDetails) { | |
| std::unique_ptr<T> baseDetails = readBaseDetails(Name()); | |
| size_t numCompactImports = getU32LEB(); | |
| while (numCompactImports--) { | |
| auto details = std::make_unique<T>(*baseDetails); | |
| details->base = getInlineString(); | |
| addImport(std::move(details)); | |
| } | |
| } | |
| void WasmBinaryReader::readImport(Name module, Name base, uint32_t kind) { | |
| switch (kind & ~BinaryConsts::ExactImport) { | |
| case ExternalKind::Function: | |
| addImport(readFunctionImport(module, base, kind)); | |
| break; | |
| case ExternalKind::Table: | |
| addImport(readTableImport(module, base)); | |
| break; | |
| case ExternalKind::Memory: | |
| addImport(readMemoryImport(module, base)); | |
| break; | |
| case ExternalKind::Global: | |
| addImport(readGlobalImport(module, base)); | |
| break; | |
| case ExternalKind::Tag: | |
| addImport(readTagImport(module, base)); | |
| break; | |
| default: | |
| throwError("bad import kind"); | |
| } | |
| } | |
| void WasmBinaryReader::readImports() { | |
| size_t num = getU32LEB(); | |
| for (size_t i = 0; i < num; i++) { | |
| auto module = getInlineString(); | |
| auto base = getInlineString(); | |
| auto kind = getInt8(); | |
| if (base == "" && (kind == BinaryConsts::CompactImportsSharedModule || | |
| kind == BinaryConsts::CompactImportsSharedAll)) { | |
| if (!wasm.features.hasCompactImports()) { | |
| throwError("compact imports not supported"); | |
| } | |
| if (kind == BinaryConsts::CompactImportsSharedModule) { | |
| size_t numCompactImports = getU32LEB(); | |
| while (numCompactImports--) { | |
| base = getInlineString(); | |
| kind = getInt8(); | |
| readImport(module, base, kind); | |
| } | |
| } else { | |
| kind = getInt8(); | |
| switch (kind & ~BinaryConsts::ExactImport) { | |
| case ExternalKind::Function: | |
| readCompactImportsShared<Function>(module, [&](Name base) { | |
| return readFunctionImport(module, base, kind); | |
| }); | |
| break; | |
| case ExternalKind::Table: | |
| readCompactImportsShared<Table>( | |
| module, [&](Name base) { return readTableImport(module, base); }); | |
| break; | |
| case ExternalKind::Memory: | |
| readCompactImportsShared<Memory>(module, [&](Name base) { | |
| return readMemoryImport(module, base); | |
| }); | |
| break; | |
| case ExternalKind::Global: | |
| readCompactImportsShared<Global>(module, [&](Name base) { | |
| return readGlobalImport(module, base); | |
| }); | |
| break; | |
| case ExternalKind::Tag: | |
| readCompactImportsShared<Tag>( | |
| module, [&](Name base) { return readTagImport(module, base); }); | |
| break; | |
| default: | |
| throwError("bad import kind"); | |
| } | |
| } | |
| } else { | |
| readImport(module, base, kind); | |
| } | |
| } | |
| numFuncImports = wasm.functions.size(); | |
| } | |
| void WasmBinaryReader::setLocalNames(Function& func, Index i) { | |
| if (auto it = localNames.find(i); it != localNames.end()) { | |
| for (auto& [local, name] : it->second) { | |
| if (local >= func.getNumLocals()) { | |
| std::cerr << "warning: local index out of bounds in name section: " | |
| << name << " at index " << local << " in function " << i | |
| << '\n'; | |
| continue; | |
| } | |
| func.setLocalName(local, name); | |
| } | |
| } | |
| } | |
| void WasmBinaryReader::setLabelNames(Index i) { | |
| if (auto it = labelNames.find(i); it != labelNames.end()) { | |
| currLabelNames = &it->second; | |
| } else { | |
| currLabelNames = nullptr; | |
| } | |
| nextLabelIndex = 0; | |
| } | |
| Name WasmBinaryReader::getNextLabelName() { | |
| auto index = nextLabelIndex++; | |
| if (currLabelNames) { | |
| if (auto it = currLabelNames->find(index); it != currLabelNames->end()) { | |
| return it->second; | |
| } | |
| } | |
| return Name(); | |
| } | |
| void WasmBinaryReader::readFunctionSignatures() { | |
| size_t num = getU32LEB(); | |
| auto numImports = wasm.functions.size(); | |
| for (auto& [index, name] : functionNames) { | |
| if (index >= num + numImports) { | |
| std::cerr << "warning: function index out of bounds in name section: " | |
| << name << " at index " << index << '\n'; | |
| } | |
| } | |
| // Also check that the function indices in the local names subsection are | |
| // in-bounds, even though we don't use them here. | |
| for (auto& [index, locals] : localNames) { | |
| if (index >= num + numImports) { | |
| std::cerr << "warning: function index out of bounds in name section: " | |
| "locals at index " | |
| << index << '\n'; | |
| } | |
| } | |
| // Likewise for the label names subsection. | |
| for (auto& [index, labels] : labelNames) { | |
| if (index >= num + numImports) { | |
| std::cerr << "warning: function index out of bounds in name section: " | |
| "labels at index " | |
| << index << '\n'; | |
| } | |
| } | |
| for (size_t i = 0; i < num; i++) { | |
| auto [name, isExplicit] = getOrMakeName( | |
| functionNames, numImports + i, makeName("", i), usedFunctionNames); | |
| auto index = getU32LEB(); | |
| HeapType type = getTypeByIndex(index); | |
| functionTypes.push_back(type); | |
| // Check that the type is a signature. | |
| getSignatureByTypeIndex(index); | |
| auto func = Builder(wasm).makeFunction( | |
| name, Type(type, NonNullable, Exact), {}, nullptr); | |
| func->hasExplicitName = isExplicit; | |
| wasm.addFunction(std::move(func)); | |
| } | |
| } | |
| HeapType WasmBinaryReader::getTypeByIndex(Index index) { | |
| if (index >= types.size()) { | |
| throwError("invalid type index " + std::to_string(index) + " / " + | |
| std::to_string(types.size())); | |
| } | |
| return types[index]; | |
| } | |
| HeapType WasmBinaryReader::getTypeByFunctionIndex(Index index) { | |
| if (index >= functionTypes.size()) { | |
| throwError("invalid function index"); | |
| } | |
| return functionTypes[index]; | |
| } | |
| Signature WasmBinaryReader::getSignatureByTypeIndex(Index index) { | |
| auto heapType = getTypeByIndex(index); | |
| if (!heapType.isSignature()) { | |
| throwError("invalid signature type " + heapType.toString()); | |
| } | |
| return heapType.getSignature(); | |
| } | |
| Signature WasmBinaryReader::getSignatureByFunctionIndex(Index index) { | |
| auto heapType = getTypeByFunctionIndex(index); | |
| if (!heapType.isSignature()) { | |
| throwError("invalid signature type " + heapType.toString()); | |
| } | |
| return heapType.getSignature(); | |
| } | |
| void WasmBinaryReader::readFunctions() { | |
| numFuncBodies = getU32LEB(); | |
| if (numFuncBodies + numFuncImports != wasm.functions.size()) { | |
| throwError("invalid function section size, must equal types"); | |
| } | |
| if (needCodeLocations) { | |
| builder.setBinaryLocation(&pos, codeSectionLocation); | |
| } | |
| for (size_t i = 0; i < numFuncBodies; i++) { | |
| auto sizePos = pos; | |
| size_t size = getU32LEB(); | |
| if (size == 0) { | |
| throwError("empty function size"); | |
| } | |
| Index endOfFunction = pos + size; | |
| auto& func = wasm.functions[numFuncImports + i]; | |
| currFunction = func.get(); | |
| if (needCodeLocations) { | |
| func->funcLocation = BinaryLocations::FunctionLocations{ | |
| BinaryLocation(sizePos - codeSectionLocation), | |
| BinaryLocation(pos - codeSectionLocation), | |
| BinaryLocation(pos - codeSectionLocation + size)}; | |
| } | |
| func->prologLocation = sourceMapReader.readDebugLocationAt(pos); | |
| readVars(); | |
| setLocalNames(*func, numFuncImports + i); | |
| setLabelNames(numFuncImports + i); | |
| { | |
| // Process the function body. Even if we are skipping function bodies we | |
| // need to not skip the start function. That contains important code for | |
| // wasm-emscripten-finalize in the form of pthread-related segment | |
| // initializations. As this is just one function, it doesn't add | |
| // significant time, so the optimization of skipping bodies is still very | |
| // useful. | |
| auto currFunctionIndex = wasm.functions.size(); | |
| bool isStart = startIndex == currFunctionIndex; | |
| if (skipFunctionBodies && !isStart) { | |
| // When skipping the function body we need to put something valid in | |
| // their place so we validate. An unreachable is always acceptable | |
| // there. | |
| func->body = Builder(wasm).makeUnreachable(); | |
| // Skip reading the contents. | |
| pos = endOfFunction; | |
| } else { | |
| auto start = builder.visitFunctionStart(func.get()); | |
| if (auto* err = start.getErr()) { | |
| throwError(err->msg); | |
| } | |
| while (pos < endOfFunction) { | |
| auto inst = readInst(); | |
| if (auto* err = inst.getErr()) { | |
| throwError(err->msg); | |
| } | |
| } | |
| if (pos != endOfFunction) { | |
| throwError("function overflowed its bounds"); | |
| } | |
| if (!builder.empty()) { | |
| throwError("expected function end"); | |
| } | |
| if (currLabelNames) { | |
| // Check that the labels named in the name section actually exist. | |
| Index maxIndex = 0; | |
| for (auto& [index, name] : *currLabelNames) { | |
| maxIndex = std::max(maxIndex, index); | |
| } | |
| if (maxIndex >= nextLabelIndex) { | |
| std::cerr << "warning: label index out of bounds in name section: " | |
| << maxIndex << " in function " << (numFuncImports + i) | |
| << '\n'; | |
| } | |
| } | |
| } | |
| } | |
| sourceMapReader.finishFunction(); | |
| TypeUpdating::handleNonDefaultableLocals(func.get(), wasm); | |
| currFunction = nullptr; | |
| currLabelNames = nullptr; | |
| } | |
| } | |
| void WasmBinaryReader::readVars() { | |
| uint32_t totalVars = 0; | |
| size_t numLocalTypes = getU32LEB(); | |
| // Use a SmallVector as in the common (MVP) case there are only 4 possible | |
| // types. | |
| SmallVector<std::pair<uint32_t, Type>, 4> decodedVars; | |
| decodedVars.reserve(numLocalTypes); | |
| for (size_t t = 0; t < numLocalTypes; t++) { | |
| auto num = getU32LEB(); | |
| if (std::ckd_add(&totalVars, totalVars, num)) { | |
| throwError("unaddressable number of locals"); | |
| } | |
| auto type = getConcreteType(); | |
| decodedVars.emplace_back(num, type); | |
| } | |
| currFunction->vars.reserve(totalVars); | |
| for (auto [num, type] : decodedVars) { | |
| while (num > 0) { | |
| currFunction->vars.push_back(type); | |
| num--; | |
| } | |
| } | |
| } | |
| Result<> WasmBinaryReader::readLoad(unsigned bytes, bool signed_, Type type) { | |
| auto [mem, align, offset, arrayType] = getMemarg(); | |
| if (arrayType) { | |
| return builder.makeArrayLoad( | |
| *arrayType, bytes, signed_, offset, align, type); | |
| } | |
| return builder.makeLoad(bytes, signed_, offset, align, type, mem); | |
| } | |
| Result<> WasmBinaryReader::readStore(unsigned bytes, Type type) { | |
| auto [mem, align, offset, arrayType] = getMemarg(); | |
| if (arrayType) { | |
| return builder.makeArrayStore(*arrayType, bytes, offset, align, type); | |
| } | |
| return builder.makeStore(bytes, offset, align, type, mem); | |
| } | |
| Result<> WasmBinaryReader::readInst() { | |
| if (auto loc = sourceMapReader.readDebugLocationAt(pos)) { | |
| builder.setDebugLocation(loc); | |
| } | |
| uint8_t code = getInt8(); | |
| switch (code) { | |
| case BinaryConsts::Block: { | |
| auto name = getNextLabelName(); | |
| return builder.makeBlock(name, getBlockType()); | |
| } | |
| case BinaryConsts::If: { | |
| // An `if` cannot hold a name in Binaryen IR, so only use the name if we | |
| // end up needing a label anyhow. | |
| auto name = getNextLabelName(); | |
| auto result = builder.makeIf(Name(), getBlockType()); | |
| builder.setScopeNameHint(name); | |
| return result; | |
| } | |
| case BinaryConsts::Loop: { | |
| auto name = getNextLabelName(); | |
| return builder.makeLoop(name, getBlockType()); | |
| } | |
| case BinaryConsts::Br: | |
| return builder.makeBreak(getU32LEB(), false); | |
| case BinaryConsts::BrIf: | |
| return builder.makeBreak(getU32LEB(), true); | |
| case BinaryConsts::BrTable: { | |
| auto numTargets = getU32LEB(); | |
| std::vector<Index> labels(numTargets); | |
| for (Index i = 0; i < numTargets; ++i) { | |
| labels[i] = getU32LEB(); | |
| } | |
| return builder.makeSwitch(labels, getU32LEB()); | |
| } | |
| case BinaryConsts::CallFunction: | |
| case BinaryConsts::RetCallFunction: | |
| return builder.makeCall(getFunctionName(getU32LEB()), | |
| code == BinaryConsts::RetCallFunction); | |
| case BinaryConsts::CallIndirect: | |
| case BinaryConsts::RetCallIndirect: { | |
| auto type = getIndexedHeapType(); | |
| auto table = getTableName(getU32LEB()); | |
| return builder.makeCallIndirect( | |
| table, type, code == BinaryConsts::RetCallIndirect); | |
| } | |
| case BinaryConsts::LocalGet: | |
| return builder.makeLocalGet(getU32LEB()); | |
| case BinaryConsts::LocalSet: | |
| return builder.makeLocalSet(getU32LEB()); | |
| case BinaryConsts::LocalTee: | |
| return builder.makeLocalTee(getU32LEB()); | |
| case BinaryConsts::GlobalGet: | |
| return builder.makeGlobalGet(getGlobalName(getU32LEB())); | |
| case BinaryConsts::GlobalSet: | |
| return builder.makeGlobalSet(getGlobalName(getU32LEB())); | |
| case BinaryConsts::Select: | |
| return builder.makeSelect(std::nullopt); | |
| case BinaryConsts::SelectWithType: { | |
| auto numTypes = getU32LEB(); | |
| std::vector<Type> types; | |
| for (Index i = 0; i < numTypes; ++i) { | |
| auto t = getType(); | |
| if (!t.isConcrete()) { | |
| return Err{"bad select type"}; | |
| } | |
| types.push_back(t); | |
| } | |
| return builder.makeSelect(Type(types)); | |
| } | |
| case BinaryConsts::Return: | |
| return builder.makeReturn(); | |
| case BinaryConsts::Nop: | |
| return builder.makeNop(); | |
| case BinaryConsts::Unreachable: | |
| return builder.makeUnreachable(); | |
| case BinaryConsts::Drop: | |
| return builder.makeDrop(); | |
| case BinaryConsts::End: | |
| return builder.visitEnd(); | |
| case BinaryConsts::Else: | |
| return builder.visitElse(); | |
| case BinaryConsts::Catch_Legacy: | |
| return builder.visitCatch(getTagName(getU32LEB())); | |
| case BinaryConsts::CatchAll_Legacy: | |
| return builder.visitCatchAll(); | |
| case BinaryConsts::Delegate: | |
| return builder.visitDelegate(getU32LEB()); | |
| case BinaryConsts::RefNull: { | |
| auto [heapType, exactness] = getHeapType(); | |
| // Exactness is allowed but doesn't matter, since we always use the bottom | |
| // heap type. | |
| return builder.makeRefNull(heapType); | |
| } | |
| case BinaryConsts::RefIsNull: | |
| return builder.makeRefIsNull(); | |
| case BinaryConsts::RefFunc: | |
| return builder.makeRefFunc(getFunctionName(getU32LEB())); | |
| case BinaryConsts::RefEq: | |
| return builder.makeRefEq(); | |
| case BinaryConsts::RefAsNonNull: | |
| return builder.makeRefAs(RefAsNonNull); | |
| case BinaryConsts::BrOnNull: | |
| return builder.makeBrOn(getU32LEB(), BrOnNull); | |
| case BinaryConsts::BrOnNonNull: | |
| return builder.makeBrOn(getU32LEB(), BrOnNonNull); | |
| case BinaryConsts::TableGet: | |
| return builder.makeTableGet(getTableName(getU32LEB())); | |
| case BinaryConsts::TableSet: | |
| return builder.makeTableSet(getTableName(getU32LEB())); | |
| case BinaryConsts::Try: { | |
| // A `try` can hold a name in the IR, like a block or a loop. | |
| auto name = getNextLabelName(); | |
| return builder.makeTry(name, getBlockType()); | |
| } | |
| case BinaryConsts::TryTable: { | |
| // As with `if`, only use the name if we end up needing a label. | |
| auto name = getNextLabelName(); | |
| auto type = getBlockType(); | |
| std::vector<Name> tags; | |
| std::vector<Index> labels; | |
| std::vector<bool> isRefs; | |
| auto numHandlers = getU32LEB(); | |
| for (Index i = 0; i < numHandlers; ++i) { | |
| uint8_t code = getInt8(); | |
| if (code == BinaryConsts::Catch || code == BinaryConsts::CatchRef) { | |
| tags.push_back(getTagName(getU32LEB())); | |
| } else { | |
| tags.push_back(Name()); | |
| } | |
| labels.push_back(getU32LEB()); | |
| isRefs.push_back(code == BinaryConsts::CatchRef || | |
| code == BinaryConsts::CatchAllRef); | |
| } | |
| auto result = builder.makeTryTable(Name(), type, tags, labels, isRefs); | |
| builder.setScopeNameHint(name); | |
| return result; | |
| } | |
| case BinaryConsts::Throw: | |
| return builder.makeThrow(getTagName(getU32LEB())); | |
| case BinaryConsts::Rethrow: | |
| return builder.makeRethrow(getU32LEB()); | |
| case BinaryConsts::ThrowRef: | |
| return builder.makeThrowRef(); | |
| case BinaryConsts::MemorySize: | |
| return builder.makeMemorySize(getMemoryName(getU32LEB())); | |
| case BinaryConsts::MemoryGrow: | |
| return builder.makeMemoryGrow(getMemoryName(getU32LEB())); | |
| case BinaryConsts::CallRef: | |
| case BinaryConsts::RetCallRef: | |
| return builder.makeCallRef(getIndexedHeapType(), | |
| code == BinaryConsts::RetCallRef); | |
| case BinaryConsts::ContNew: | |
| return builder.makeContNew(getIndexedHeapType()); | |
| case BinaryConsts::ContBind: { | |
| auto before = getIndexedHeapType(); | |
| auto after = getIndexedHeapType(); | |
| return builder.makeContBind(before, after); | |
| } | |
| case BinaryConsts::Suspend: | |
| return builder.makeSuspend(getTagName(getU32LEB())); | |
| case BinaryConsts::Resume: { | |
| auto type = getIndexedHeapType(); | |
| auto numHandlers = getU32LEB(); | |
| std::vector<Name> tags; | |
| std::vector<std::optional<Index>> labels; | |
| tags.reserve(numHandlers); | |
| labels.reserve(numHandlers); | |
| for (Index i = 0; i < numHandlers; ++i) { | |
| uint8_t code = getInt8(); | |
| if (code == BinaryConsts::OnLabel) { | |
| tags.push_back(getTagName(getU32LEB())); | |
| labels.push_back(std::optional<Index>{getU32LEB()}); | |
| } else if (code == BinaryConsts::OnSwitch) { | |
| tags.push_back(getTagName(getU32LEB())); | |
| labels.push_back(std::nullopt); | |
| } else { | |
| return Err{"ON opcode expected"}; | |
| } | |
| } | |
| return builder.makeResume(type, tags, labels); | |
| } | |
| case BinaryConsts::ResumeThrow: | |
| case BinaryConsts::ResumeThrowRef: { | |
| auto type = getIndexedHeapType(); | |
| Name tag; | |
| if (code == BinaryConsts::ResumeThrow) { | |
| tag = getTagName(getU32LEB()); | |
| } | |
| auto numHandlers = getU32LEB(); | |
| std::vector<Name> tags; | |
| std::vector<std::optional<Index>> labels; | |
| tags.reserve(numHandlers); | |
| labels.reserve(numHandlers); | |
| for (Index i = 0; i < numHandlers; ++i) { | |
| uint8_t code = getInt8(); | |
| if (code == BinaryConsts::OnLabel) { | |
| tags.push_back(getTagName(getU32LEB())); | |
| labels.push_back(std::optional<Index>{getU32LEB()}); | |
| } else if (code == BinaryConsts::OnSwitch) { | |
| tags.push_back(getTagName(getU32LEB())); | |
| labels.push_back(std::nullopt); | |
| } else { | |
| return Err{"ON opcode expected"}; | |
| } | |
| } | |
| return builder.makeResumeThrow(type, tag, tags, labels); | |
| } | |
| case BinaryConsts::Switch: { | |
| auto type = getIndexedHeapType(); | |
| auto tag = getTagName(getU32LEB()); | |
| return builder.makeStackSwitch(type, tag); | |
| } | |
| #define BINARY_INT(code) \ | |
| case BinaryConsts::I32##code: \ | |
| return builder.makeBinary(code##Int32); \ | |
| case BinaryConsts::I64##code: \ | |
| return builder.makeBinary(code##Int64); | |
| #define BINARY_FLOAT(code) \ | |
| case BinaryConsts::F32##code: \ | |
| return builder.makeBinary(code##Float32); \ | |
| case BinaryConsts::F64##code: \ | |
| return builder.makeBinary(code##Float64); | |
| #define BINARY_NUM(code) \ | |
| BINARY_INT(code) \ | |
| BINARY_FLOAT(code) | |
| BINARY_NUM(Add); | |
| BINARY_NUM(Sub); | |
| BINARY_NUM(Mul); | |
| BINARY_INT(DivS); | |
| BINARY_INT(DivU); | |
| BINARY_INT(RemS); | |
| BINARY_INT(RemU); | |
| BINARY_INT(And); | |
| BINARY_INT(Or); | |
| BINARY_INT(Xor); | |
| BINARY_INT(Shl); | |
| BINARY_INT(ShrU); | |
| BINARY_INT(ShrS); | |
| BINARY_INT(RotL); | |
| BINARY_INT(RotR); | |
| BINARY_FLOAT(Div); | |
| BINARY_FLOAT(CopySign); | |
| BINARY_FLOAT(Min); | |
| BINARY_FLOAT(Max); | |
| BINARY_NUM(Eq); | |
| BINARY_NUM(Ne); | |
| BINARY_INT(LtS); | |
| BINARY_INT(LtU); | |
| BINARY_INT(LeS); | |
| BINARY_INT(LeU); | |
| BINARY_INT(GtS); | |
| BINARY_INT(GtU); | |
| BINARY_INT(GeS); | |
| BINARY_INT(GeU); | |
| BINARY_FLOAT(Lt); | |
| BINARY_FLOAT(Le); | |
| BINARY_FLOAT(Gt); | |
| BINARY_FLOAT(Ge); | |
| #define UNARY_INT(code) \ | |
| case BinaryConsts::I32##code: \ | |
| return builder.makeUnary(code##Int32); \ | |
| case BinaryConsts::I64##code: \ | |
| return builder.makeUnary(code##Int64); | |
| #define UNARY_FLOAT(code) \ | |
| case BinaryConsts::F32##code: \ | |
| return builder.makeUnary(code##Float32); \ | |
| case BinaryConsts::F64##code: \ | |
| return builder.makeUnary(code##Float64); | |
| UNARY_INT(Clz); | |
| UNARY_INT(Ctz); | |
| UNARY_INT(Popcnt); | |
| UNARY_INT(EqZ); | |
| UNARY_FLOAT(Neg); | |
| UNARY_FLOAT(Abs); | |
| UNARY_FLOAT(Ceil); | |
| UNARY_FLOAT(Floor); | |
| UNARY_FLOAT(Nearest); | |
| UNARY_FLOAT(Sqrt); | |
| case BinaryConsts::F32UConvertI32: | |
| return builder.makeUnary(ConvertUInt32ToFloat32); | |
| case BinaryConsts::F64UConvertI32: | |
| return builder.makeUnary(ConvertUInt32ToFloat64); | |
| case BinaryConsts::F32SConvertI32: | |
| return builder.makeUnary(ConvertSInt32ToFloat32); | |
| case BinaryConsts::F64SConvertI32: | |
| return builder.makeUnary(ConvertSInt32ToFloat64); | |
| case BinaryConsts::F32UConvertI64: | |
| return builder.makeUnary(ConvertUInt64ToFloat32); | |
| case BinaryConsts::F64UConvertI64: | |
| return builder.makeUnary(ConvertUInt64ToFloat64); | |
| case BinaryConsts::F32SConvertI64: | |
| return builder.makeUnary(ConvertSInt64ToFloat32); | |
| case BinaryConsts::F64SConvertI64: | |
| return builder.makeUnary(ConvertSInt64ToFloat64); | |
| case BinaryConsts::I64SExtendI32: | |
| return builder.makeUnary(ExtendSInt32); | |
| case BinaryConsts::I64UExtendI32: | |
| return builder.makeUnary(ExtendUInt32); | |
| case BinaryConsts::I32WrapI64: | |
| return builder.makeUnary(WrapInt64); | |
| case BinaryConsts::I32UTruncF32: | |
| return builder.makeUnary(TruncUFloat32ToInt32); | |
| case BinaryConsts::I32UTruncF64: | |
| return builder.makeUnary(TruncUFloat64ToInt32); | |
| case BinaryConsts::I32STruncF32: | |
| return builder.makeUnary(TruncSFloat32ToInt32); | |
| case BinaryConsts::I32STruncF64: | |
| return builder.makeUnary(TruncSFloat64ToInt32); | |
| case BinaryConsts::I64UTruncF32: | |
| return builder.makeUnary(TruncUFloat32ToInt64); | |
| case BinaryConsts::I64UTruncF64: | |
| return builder.makeUnary(TruncUFloat64ToInt64); | |
| case BinaryConsts::I64STruncF32: | |
| return builder.makeUnary(TruncSFloat32ToInt64); | |
| case BinaryConsts::I64STruncF64: | |
| return builder.makeUnary(TruncSFloat64ToInt64); | |
| case BinaryConsts::F32Trunc: | |
| return builder.makeUnary(TruncFloat32); | |
| case BinaryConsts::F64Trunc: | |
| return builder.makeUnary(TruncFloat64); | |
| case BinaryConsts::F32DemoteI64: | |
| return builder.makeUnary(DemoteFloat64); | |
| case BinaryConsts::F64PromoteF32: | |
| return builder.makeUnary(PromoteFloat32); | |
| case BinaryConsts::I32ReinterpretF32: | |
| return builder.makeUnary(ReinterpretFloat32); | |
| case BinaryConsts::I64ReinterpretF64: | |
| return builder.makeUnary(ReinterpretFloat64); | |
| case BinaryConsts::F32ReinterpretI32: | |
| return builder.makeUnary(ReinterpretInt32); | |
| case BinaryConsts::F64ReinterpretI64: | |
| return builder.makeUnary(ReinterpretInt64); | |
| case BinaryConsts::I32ExtendS8: | |
| return builder.makeUnary(ExtendS8Int32); | |
| case BinaryConsts::I32ExtendS16: | |
| return builder.makeUnary(ExtendS16Int32); | |
| case BinaryConsts::I64ExtendS8: | |
| return builder.makeUnary(ExtendS8Int64); | |
| case BinaryConsts::I64ExtendS16: | |
| return builder.makeUnary(ExtendS16Int64); | |
| case BinaryConsts::I64ExtendS32: | |
| return builder.makeUnary(ExtendS32Int64); | |
| case BinaryConsts::I32Const: | |
| return builder.makeConst(Literal(getS32LEB())); | |
| case BinaryConsts::I64Const: | |
| return builder.makeConst(Literal(getS64LEB())); | |
| case BinaryConsts::F32Const: | |
| return builder.makeConst(getFloat32Literal()); | |
| case BinaryConsts::F64Const: | |
| return builder.makeConst(getFloat64Literal()); | |
| case BinaryConsts::I32LoadMem8S: | |
| return readLoad(1, true, Type::i32); | |
| case BinaryConsts::I32LoadMem8U: | |
| return readLoad(1, false, Type::i32); | |
| case BinaryConsts::I32LoadMem16S: | |
| return readLoad(2, true, Type::i32); | |
| case BinaryConsts::I32LoadMem16U: | |
| return readLoad(2, false, Type::i32); | |
| case BinaryConsts::I32LoadMem: | |
| return readLoad(4, false, Type::i32); | |
| case BinaryConsts::I64LoadMem8S: | |
| return readLoad(1, true, Type::i64); | |
| case BinaryConsts::I64LoadMem8U: | |
| return readLoad(1, false, Type::i64); | |
| case BinaryConsts::I64LoadMem16S: | |
| return readLoad(2, true, Type::i64); | |
| case BinaryConsts::I64LoadMem16U: | |
| return readLoad(2, false, Type::i64); | |
| case BinaryConsts::I64LoadMem32S: | |
| return readLoad(4, true, Type::i64); | |
| case BinaryConsts::I64LoadMem32U: | |
| return readLoad(4, false, Type::i64); | |
| case BinaryConsts::I64LoadMem: | |
| return readLoad(8, false, Type::i64); | |
| case BinaryConsts::F32LoadMem: | |
| return readLoad(4, false, Type::f32); | |
| case BinaryConsts::F64LoadMem: | |
| return readLoad(8, false, Type::f64); | |
| case BinaryConsts::I32StoreMem8: { | |
| return readStore(1, Type::i32); | |
| } | |
| case BinaryConsts::I32StoreMem16: { | |
| return readStore(2, Type::i32); | |
| } | |
| case BinaryConsts::I32StoreMem: { | |
| return readStore(4, Type::i32); | |
| } | |
| case BinaryConsts::I64StoreMem8: { | |
| return readStore(1, Type::i64); | |
| } | |
| case BinaryConsts::I64StoreMem16: { | |
| return readStore(2, Type::i64); | |
| } | |
| case BinaryConsts::I64StoreMem32: { | |
| return readStore(4, Type::i64); | |
| } | |
| case BinaryConsts::I64StoreMem: { | |
| return readStore(8, Type::i64); | |
| } | |
| case BinaryConsts::F32StoreMem: { | |
| return readStore(4, Type::f32); | |
| } | |
| case BinaryConsts::F64StoreMem: { | |
| return readStore(8, Type::f64); | |
| } | |
| case BinaryConsts::AtomicPrefix: { | |
| auto op = getU32LEB(); | |
| switch (op) { | |
| case BinaryConsts::I32AtomicLoad8U: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicLoad( | |
| 1, offset, align, Type::i32, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I32AtomicLoad16U: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicLoad( | |
| 2, offset, align, Type::i32, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I32AtomicLoad: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicLoad( | |
| 4, offset, align, Type::i32, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I64AtomicLoad8U: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicLoad( | |
| 1, offset, align, Type::i64, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I64AtomicLoad16U: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicLoad( | |
| 2, offset, align, Type::i64, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I64AtomicLoad32U: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicLoad( | |
| 4, offset, align, Type::i64, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I64AtomicLoad: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicLoad( | |
| 8, offset, align, Type::i64, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I32AtomicStore8: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicStore( | |
| 1, offset, align, Type::i32, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I32AtomicStore16: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicStore( | |
| 2, offset, align, Type::i32, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I32AtomicStore: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicStore( | |
| 4, offset, align, Type::i32, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I64AtomicStore8: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicStore( | |
| 1, offset, align, Type::i64, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I64AtomicStore16: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicStore( | |
| 2, offset, align, Type::i64, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I64AtomicStore32: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicStore( | |
| 4, offset, align, Type::i64, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I64AtomicStore: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicStore( | |
| 8, offset, align, Type::i64, mem, memoryOrder); | |
| } | |
| #define RMW(op) \ | |
| case BinaryConsts::I32AtomicRMW##op: { \ | |
| auto [mem, align, offset, memoryOrder] = getRMWMemarg(); \ | |
| return builder.makeAtomicRMW( \ | |
| RMW##op, 4, offset, align, Type::i32, mem, memoryOrder); \ | |
| } \ | |
| case BinaryConsts::I32AtomicRMW##op##8U: { \ | |
| auto [mem, align, offset, memoryOrder] = getRMWMemarg(); \ | |
| return builder.makeAtomicRMW( \ | |
| RMW##op, 1, offset, align, Type::i32, mem, memoryOrder); \ | |
| } \ | |
| case BinaryConsts::I32AtomicRMW##op##16U: { \ | |
| auto [mem, align, offset, memoryOrder] = getRMWMemarg(); \ | |
| return builder.makeAtomicRMW( \ | |
| RMW##op, 2, offset, align, Type::i32, mem, memoryOrder); \ | |
| } \ | |
| case BinaryConsts::I64AtomicRMW##op: { \ | |
| auto [mem, align, offset, memoryOrder] = getRMWMemarg(); \ | |
| return builder.makeAtomicRMW( \ | |
| RMW##op, 8, offset, align, Type::i64, mem, memoryOrder); \ | |
| } \ | |
| case BinaryConsts::I64AtomicRMW##op##8U: { \ | |
| auto [mem, align, offset, memoryOrder] = getRMWMemarg(); \ | |
| return builder.makeAtomicRMW( \ | |
| RMW##op, 1, offset, align, Type::i64, mem, memoryOrder); \ | |
| } \ | |
| case BinaryConsts::I64AtomicRMW##op##16U: { \ | |
| auto [mem, align, offset, memoryOrder] = getRMWMemarg(); \ | |
| return builder.makeAtomicRMW( \ | |
| RMW##op, 2, offset, align, Type::i64, mem, memoryOrder); \ | |
| } \ | |
| case BinaryConsts::I64AtomicRMW##op##32U: { \ | |
| auto [mem, align, offset, memoryOrder] = getRMWMemarg(); \ | |
| return builder.makeAtomicRMW( \ | |
| RMW##op, 4, offset, align, Type::i64, mem, memoryOrder); \ | |
| } | |
| RMW(Add); | |
| RMW(Sub); | |
| RMW(And); | |
| RMW(Or); | |
| RMW(Xor); | |
| RMW(Xchg); | |
| case BinaryConsts::I32AtomicCmpxchg: { | |
| auto [mem, align, offset, memoryOrder] = getRMWMemarg(); | |
| return builder.makeAtomicCmpxchg( | |
| 4, offset, align, Type::i32, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I32AtomicCmpxchg8U: { | |
| auto [mem, align, offset, memoryOrder] = getRMWMemarg(); | |
| return builder.makeAtomicCmpxchg( | |
| 1, offset, align, Type::i32, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I32AtomicCmpxchg16U: { | |
| auto [mem, align, offset, memoryOrder] = getRMWMemarg(); | |
| return builder.makeAtomicCmpxchg( | |
| 2, offset, align, Type::i32, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I64AtomicCmpxchg: { | |
| auto [mem, align, offset, memoryOrder] = getRMWMemarg(); | |
| return builder.makeAtomicCmpxchg( | |
| 8, offset, align, Type::i64, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I64AtomicCmpxchg8U: { | |
| auto [mem, align, offset, memoryOrder] = getRMWMemarg(); | |
| return builder.makeAtomicCmpxchg( | |
| 1, offset, align, Type::i64, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I64AtomicCmpxchg16U: { | |
| auto [mem, align, offset, memoryOrder] = getRMWMemarg(); | |
| return builder.makeAtomicCmpxchg( | |
| 2, offset, align, Type::i64, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I64AtomicCmpxchg32U: { | |
| auto [mem, align, offset, memoryOrder] = getRMWMemarg(); | |
| return builder.makeAtomicCmpxchg( | |
| 4, offset, align, Type::i64, mem, memoryOrder); | |
| } | |
| case BinaryConsts::I32AtomicWait: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicWait(Type::i32, offset, align, mem); | |
| } | |
| case BinaryConsts::I64AtomicWait: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicWait(Type::i64, offset, align, mem); | |
| } | |
| case BinaryConsts::AtomicNotify: { | |
| auto [mem, align, offset, memoryOrder] = getAtomicMemarg(); | |
| return builder.makeAtomicNotify(offset, align, mem); | |
| } | |
| case BinaryConsts::AtomicFence: { | |
| MemoryOrder order = getMemoryOrder(/*isRMW=*/false); | |
| return builder.makeAtomicFence(order); | |
| } | |
| case BinaryConsts::Pause: | |
| return builder.makePause(); | |
| case BinaryConsts::StructAtomicGet: | |
| case BinaryConsts::StructAtomicGetS: | |
| case BinaryConsts::StructAtomicGetU: { | |
| auto order = getMemoryOrder(); | |
| auto type = getIndexedHeapType(); | |
| auto field = getU32LEB(); | |
| bool signed_ = op == BinaryConsts::StructAtomicGetS; | |
| return builder.makeStructGet(type, field, signed_, order); | |
| } | |
| case BinaryConsts::StructAtomicSet: { | |
| auto order = getMemoryOrder(); | |
| auto type = getIndexedHeapType(); | |
| auto field = getU32LEB(); | |
| return builder.makeStructSet(type, field, order); | |
| } | |
| #define STRUCT_RMW(op) \ | |
| case BinaryConsts::StructAtomicRMW##op: { \ | |
| auto order = getMemoryOrder(true); \ | |
| auto type = getIndexedHeapType(); \ | |
| auto field = getU32LEB(); \ | |
| return builder.makeStructRMW(RMW##op, type, field, order); \ | |
| } | |
| STRUCT_RMW(Add) | |
| STRUCT_RMW(Sub) | |
| STRUCT_RMW(And) | |
| STRUCT_RMW(Or) | |
| STRUCT_RMW(Xor) | |
| STRUCT_RMW(Xchg) | |
| case BinaryConsts::StructAtomicRMWCmpxchg: { | |
| auto order = getMemoryOrder(true); | |
| auto type = getIndexedHeapType(); | |
| auto field = getU32LEB(); | |
| return builder.makeStructCmpxchg(type, field, order); | |
| } | |
| case BinaryConsts::ArrayAtomicGet: | |
| case BinaryConsts::ArrayAtomicGetS: | |
| case BinaryConsts::ArrayAtomicGetU: { | |
| auto order = getMemoryOrder(); | |
| auto type = getIndexedHeapType(); | |
| bool signed_ = op == BinaryConsts::ArrayAtomicGetS; | |
| return builder.makeArrayGet(type, signed_, order); | |
| } | |
| case BinaryConsts::ArrayAtomicSet: { | |
| auto order = getMemoryOrder(); | |
| auto type = getIndexedHeapType(); | |
| return builder.makeArraySet(type, order); | |
| } | |
| #define ARRAY_RMW(op) \ | |
| case BinaryConsts::ArrayAtomicRMW##op: { \ | |
| auto order = getMemoryOrder(true); \ | |
| auto type = getIndexedHeapType(); \ | |
| return builder.makeArrayRMW(RMW##op, type, order); \ | |
| } | |
| ARRAY_RMW(Add) | |
| ARRAY_RMW(Sub) | |
| ARRAY_RMW(And) | |
| ARRAY_RMW(Or) | |
| ARRAY_RMW(Xor) | |
| ARRAY_RMW(Xchg) | |
| case BinaryConsts::ArrayAtomicRMWCmpxchg: { | |
| auto order = getMemoryOrder(true); | |
| auto type = getIndexedHeapType(); | |
| return builder.makeArrayCmpxchg(type, order); | |
| } | |
| case BinaryConsts::StructWait: { | |
| auto structType = getIndexedHeapType(); | |
| auto index = getU32LEB(); | |
| return builder.makeStructWait(structType, index); | |
| } | |
| case BinaryConsts::WaitqueueNotify: { | |
| return builder.makeWaitqueueNotify(); | |
| } | |
| case BinaryConsts::WaitqueueNew: { | |
| return builder.makeWaitqueueNew(); | |
| } | |
| case BinaryConsts::Publish: { | |
| return builder.makePublish(); | |
| } | |
| } | |
| return Err{"unknown atomic operation " + std::to_string(op)}; | |
| } | |
| case BinaryConsts::MiscPrefix: { | |
| auto op = getU32LEB(); | |
| switch (op) { | |
| case BinaryConsts::I32STruncSatF32: | |
| return builder.makeUnary(TruncSatSFloat32ToInt32); | |
| case BinaryConsts::I32UTruncSatF32: | |
| return builder.makeUnary(TruncSatUFloat32ToInt32); | |
| case BinaryConsts::I32STruncSatF64: | |
| return builder.makeUnary(TruncSatSFloat64ToInt32); | |
| case BinaryConsts::I32UTruncSatF64: | |
| return builder.makeUnary(TruncSatUFloat64ToInt32); | |
| case BinaryConsts::I64STruncSatF32: | |
| return builder.makeUnary(TruncSatSFloat32ToInt64); | |
| case BinaryConsts::I64UTruncSatF32: | |
| return builder.makeUnary(TruncSatUFloat32ToInt64); | |
| case BinaryConsts::I64STruncSatF64: | |
| return builder.makeUnary(TruncSatSFloat64ToInt64); | |
| case BinaryConsts::I64UTruncSatF64: | |
| return builder.makeUnary(TruncSatUFloat64ToInt64); | |
| case BinaryConsts::MemoryInit: { | |
| auto data = getDataName(getU32LEB()); | |
| auto mem = getMemoryName(getU32LEB()); | |
| return builder.makeMemoryInit(data, mem); | |
| } | |
| case BinaryConsts::DataDrop: | |
| return builder.makeDataDrop(getDataName(getU32LEB())); | |
| case BinaryConsts::MemoryCopy: { | |
| auto dest = getMemoryName(getU32LEB()); | |
| auto src = getMemoryName(getU32LEB()); | |
| return builder.makeMemoryCopy(dest, src); | |
| } | |
| case BinaryConsts::MemoryFill: | |
| return builder.makeMemoryFill(getMemoryName(getU32LEB())); | |
| case BinaryConsts::I64Add128: | |
| return builder.makeWideIntAddSub(AddInt128); | |
| case BinaryConsts::I64Sub128: | |
| return builder.makeWideIntAddSub(SubInt128); | |
| case BinaryConsts::I64MulWideS: | |
| return builder.makeWideIntMul(MulWideSInt64); | |
| case BinaryConsts::I64MulWideU: | |
| return builder.makeWideIntMul(MulWideUInt64); | |
| case BinaryConsts::TableSize: | |
| return builder.makeTableSize(getTableName(getU32LEB())); | |
| case BinaryConsts::TableGrow: | |
| return builder.makeTableGrow(getTableName(getU32LEB())); | |
| case BinaryConsts::TableFill: | |
| return builder.makeTableFill(getTableName(getU32LEB())); | |
| case BinaryConsts::TableCopy: { | |
| auto dest = getTableName(getU32LEB()); | |
| auto src = getTableName(getU32LEB()); | |
| return builder.makeTableCopy(dest, src); | |
| } | |
| case BinaryConsts::TableInit: { | |
| auto elem = getElemName(getU32LEB()); | |
| auto table = getTableName(getU32LEB()); | |
| return builder.makeTableInit(elem, table); | |
| } | |
| case BinaryConsts::ElemDrop: { | |
| auto elem = getElemName(getU32LEB()); | |
| return builder.makeElemDrop(elem); | |
| } | |
| case BinaryConsts::F32_F16LoadMem: { | |
| return readLoad(2, false, Type::f32); | |
| } | |
| case BinaryConsts::F32_F16StoreMem: { | |
| return readStore(2, Type::f32); | |
| } | |
| } | |
| return Err{"unknown misc operation: " + std::to_string(op)}; | |
| } | |
| case BinaryConsts::SIMDPrefix: { | |
| auto op = getU32LEB(); | |
| switch (op) { | |
| case BinaryConsts::I8x16Eq: | |
| return builder.makeBinary(EqVecI8x16); | |
| case BinaryConsts::I8x16Ne: | |
| return builder.makeBinary(NeVecI8x16); | |
| case BinaryConsts::I8x16LtS: | |
| return builder.makeBinary(LtSVecI8x16); | |
| case BinaryConsts::I8x16LtU: | |
| return builder.makeBinary(LtUVecI8x16); | |
| case BinaryConsts::I8x16GtS: | |
| return builder.makeBinary(GtSVecI8x16); | |
| case BinaryConsts::I8x16GtU: | |
| return builder.makeBinary(GtUVecI8x16); | |
| case BinaryConsts::I8x16LeS: | |
| return builder.makeBinary(LeSVecI8x16); | |
| case BinaryConsts::I8x16LeU: | |
| return builder.makeBinary(LeUVecI8x16); | |
| case BinaryConsts::I8x16GeS: | |
| return builder.makeBinary(GeSVecI8x16); | |
| case BinaryConsts::I8x16GeU: | |
| return builder.makeBinary(GeUVecI8x16); | |
| case BinaryConsts::I16x8Eq: | |
| return builder.makeBinary(EqVecI16x8); | |
| case BinaryConsts::I16x8Ne: | |
| return builder.makeBinary(NeVecI16x8); | |
| case BinaryConsts::I16x8LtS: | |
| return builder.makeBinary(LtSVecI16x8); | |
| case BinaryConsts::I16x8LtU: | |
| return builder.makeBinary(LtUVecI16x8); | |
| case BinaryConsts::I16x8GtS: | |
| return builder.makeBinary(GtSVecI16x8); | |
| case BinaryConsts::I16x8GtU: | |
| return builder.makeBinary(GtUVecI16x8); | |
| case BinaryConsts::I16x8LeS: | |
| return builder.makeBinary(LeSVecI16x8); | |
| case BinaryConsts::I16x8LeU: | |
| return builder.makeBinary(LeUVecI16x8); | |
| case BinaryConsts::I16x8GeS: | |
| return builder.makeBinary(GeSVecI16x8); | |
| case BinaryConsts::I16x8GeU: | |
| return builder.makeBinary(GeUVecI16x8); | |
| case BinaryConsts::I32x4Eq: | |
| return builder.makeBinary(EqVecI32x4); | |
| case BinaryConsts::I32x4Ne: | |
| return builder.makeBinary(NeVecI32x4); | |
| case BinaryConsts::I32x4LtS: | |
| return builder.makeBinary(LtSVecI32x4); | |
| case BinaryConsts::I32x4LtU: | |
| return builder.makeBinary(LtUVecI32x4); | |
| case BinaryConsts::I32x4GtS: | |
| return builder.makeBinary(GtSVecI32x4); | |
| case BinaryConsts::I32x4GtU: | |
| return builder.makeBinary(GtUVecI32x4); | |
| case BinaryConsts::I32x4LeS: | |
| return builder.makeBinary(LeSVecI32x4); | |
| case BinaryConsts::I32x4LeU: | |
| return builder.makeBinary(LeUVecI32x4); | |
| case BinaryConsts::I32x4GeS: | |
| return builder.makeBinary(GeSVecI32x4); | |
| case BinaryConsts::I32x4GeU: | |
| return builder.makeBinary(GeUVecI32x4); | |
| case BinaryConsts::I64x2Eq: | |
| return builder.makeBinary(EqVecI64x2); | |
| case BinaryConsts::I64x2Ne: | |
| return builder.makeBinary(NeVecI64x2); | |
| case BinaryConsts::I64x2LtS: | |
| return builder.makeBinary(LtSVecI64x2); | |
| case BinaryConsts::I64x2GtS: | |
| return builder.makeBinary(GtSVecI64x2); | |
| case BinaryConsts::I64x2LeS: | |
| return builder.makeBinary(LeSVecI64x2); | |
| case BinaryConsts::I64x2GeS: | |
| return builder.makeBinary(GeSVecI64x2); | |
| case BinaryConsts::F16x8Eq: | |
| return builder.makeBinary(EqVecF16x8); | |
| case BinaryConsts::F16x8Ne: | |
| return builder.makeBinary(NeVecF16x8); | |
| case BinaryConsts::F16x8Lt: | |
| return builder.makeBinary(LtVecF16x8); | |
| case BinaryConsts::F16x8Gt: | |
| return builder.makeBinary(GtVecF16x8); | |
| case BinaryConsts::F16x8Le: | |
| return builder.makeBinary(LeVecF16x8); | |
| case BinaryConsts::F16x8Ge: | |
| return builder.makeBinary(GeVecF16x8); | |
| case BinaryConsts::F32x4Eq: | |
| return builder.makeBinary(EqVecF32x4); | |
| case BinaryConsts::F32x4Ne: | |
| return builder.makeBinary(NeVecF32x4); | |
| case BinaryConsts::F32x4Lt: | |
| return builder.makeBinary(LtVecF32x4); | |
| case BinaryConsts::F32x4Gt: | |
| return builder.makeBinary(GtVecF32x4); | |
| case BinaryConsts::F32x4Le: | |
| return builder.makeBinary(LeVecF32x4); | |
| case BinaryConsts::F32x4Ge: | |
| return builder.makeBinary(GeVecF32x4); | |
| case BinaryConsts::F64x2Eq: | |
| return builder.makeBinary(EqVecF64x2); | |
| case BinaryConsts::F64x2Ne: | |
| return builder.makeBinary(NeVecF64x2); | |
| case BinaryConsts::F64x2Lt: | |
| return builder.makeBinary(LtVecF64x2); | |
| case BinaryConsts::F64x2Gt: | |
| return builder.makeBinary(GtVecF64x2); | |
| case BinaryConsts::F64x2Le: | |
| return builder.makeBinary(LeVecF64x2); | |
| case BinaryConsts::F64x2Ge: | |
| return builder.makeBinary(GeVecF64x2); | |
| case BinaryConsts::V128And: | |
| return builder.makeBinary(AndVec128); | |
| case BinaryConsts::V128Or: | |
| return builder.makeBinary(OrVec128); | |
| case BinaryConsts::V128Xor: | |
| return builder.makeBinary(XorVec128); | |
| case BinaryConsts::V128Andnot: | |
| return builder.makeBinary(AndNotVec128); | |
| case BinaryConsts::I8x16Add: | |
| return builder.makeBinary(AddVecI8x16); | |
| case BinaryConsts::I8x16AddSatS: | |
| return builder.makeBinary(AddSatSVecI8x16); | |
| case BinaryConsts::I8x16AddSatU: | |
| return builder.makeBinary(AddSatUVecI8x16); | |
| case BinaryConsts::I8x16Sub: | |
| return builder.makeBinary(SubVecI8x16); | |
| case BinaryConsts::I8x16SubSatS: | |
| return builder.makeBinary(SubSatSVecI8x16); | |
| case BinaryConsts::I8x16SubSatU: | |
| return builder.makeBinary(SubSatUVecI8x16); | |
| case BinaryConsts::I8x16MinS: | |
| return builder.makeBinary(MinSVecI8x16); | |
| case BinaryConsts::I8x16MinU: | |
| return builder.makeBinary(MinUVecI8x16); | |
| case BinaryConsts::I8x16MaxS: | |
| return builder.makeBinary(MaxSVecI8x16); | |
| case BinaryConsts::I8x16MaxU: | |
| return builder.makeBinary(MaxUVecI8x16); | |
| case BinaryConsts::I8x16AvgrU: | |
| return builder.makeBinary(AvgrUVecI8x16); | |
| case BinaryConsts::I16x8Add: | |
| return builder.makeBinary(AddVecI16x8); | |
| case BinaryConsts::I16x8AddSatS: | |
| return builder.makeBinary(AddSatSVecI16x8); | |
| case BinaryConsts::I16x8AddSatU: | |
| return builder.makeBinary(AddSatUVecI16x8); | |
| case BinaryConsts::I16x8Sub: | |
| return builder.makeBinary(SubVecI16x8); | |
| case BinaryConsts::I16x8SubSatS: | |
| return builder.makeBinary(SubSatSVecI16x8); | |
| case BinaryConsts::I16x8SubSatU: | |
| return builder.makeBinary(SubSatUVecI16x8); | |
| case BinaryConsts::I16x8Mul: | |
| return builder.makeBinary(MulVecI16x8); | |
| case BinaryConsts::I16x8MinS: | |
| return builder.makeBinary(MinSVecI16x8); | |
| case BinaryConsts::I16x8MinU: | |
| return builder.makeBinary(MinUVecI16x8); | |
| case BinaryConsts::I16x8MaxS: | |
| return builder.makeBinary(MaxSVecI16x8); | |
| case BinaryConsts::I16x8MaxU: | |
| return builder.makeBinary(MaxUVecI16x8); | |
| case BinaryConsts::I16x8AvgrU: | |
| return builder.makeBinary(AvgrUVecI16x8); | |
| case BinaryConsts::I16x8Q15MulrSatS: | |
| return builder.makeBinary(Q15MulrSatSVecI16x8); | |
| case BinaryConsts::I16x8ExtmulLowI8x16S: | |
| return builder.makeBinary(ExtMulLowSVecI16x8); | |
| case BinaryConsts::I16x8ExtmulHighI8x16S: | |
| return builder.makeBinary(ExtMulHighSVecI16x8); | |
| case BinaryConsts::I16x8ExtmulLowI8x16U: | |
| return builder.makeBinary(ExtMulLowUVecI16x8); | |
| case BinaryConsts::I16x8ExtmulHighI8x16U: | |
| return builder.makeBinary(ExtMulHighUVecI16x8); | |
| case BinaryConsts::I32x4Add: | |
| return builder.makeBinary(AddVecI32x4); | |
| case BinaryConsts::I32x4Sub: | |
| return builder.makeBinary(SubVecI32x4); | |
| case BinaryConsts::I32x4Mul: | |
| return builder.makeBinary(MulVecI32x4); | |
| case BinaryConsts::I32x4MinS: | |
| return builder.makeBinary(MinSVecI32x4); | |
| case BinaryConsts::I32x4MinU: | |
| return builder.makeBinary(MinUVecI32x4); | |
| case BinaryConsts::I32x4MaxS: | |
| return builder.makeBinary(MaxSVecI32x4); | |
| case BinaryConsts::I32x4MaxU: | |
| return builder.makeBinary(MaxUVecI32x4); | |
| case BinaryConsts::I32x4DotI16x8S: | |
| return builder.makeBinary(DotSVecI16x8ToVecI32x4); | |
| case BinaryConsts::I32x4ExtmulLowI16x8S: | |
| return builder.makeBinary(ExtMulLowSVecI32x4); | |
| case BinaryConsts::I32x4ExtmulHighI16x8S: | |
| return builder.makeBinary(ExtMulHighSVecI32x4); | |
| case BinaryConsts::I32x4ExtmulLowI16x8U: | |
| return builder.makeBinary(ExtMulLowUVecI32x4); | |
| case BinaryConsts::I32x4ExtmulHighI16x8U: | |
| return builder.makeBinary(ExtMulHighUVecI32x4); | |
| case BinaryConsts::I64x2Add: | |
| return builder.makeBinary(AddVecI64x2); | |
| case BinaryConsts::I64x2Sub: | |
| return builder.makeBinary(SubVecI64x2); | |
| case BinaryConsts::I64x2Mul: | |
| return builder.makeBinary(MulVecI64x2); | |
| case BinaryConsts::I64x2ExtmulLowI32x4S: | |
| return builder.makeBinary(ExtMulLowSVecI64x2); | |
| case BinaryConsts::I64x2ExtmulHighI32x4S: | |
| return builder.makeBinary(ExtMulHighSVecI64x2); | |
| case BinaryConsts::I64x2ExtmulLowI32x4U: | |
| return builder.makeBinary(ExtMulLowUVecI64x2); | |
| case BinaryConsts::I64x2ExtmulHighI32x4U: | |
| return builder.makeBinary(ExtMulHighUVecI64x2); | |
| case BinaryConsts::F16x8Add: | |
| return builder.makeBinary(AddVecF16x8); | |
| case BinaryConsts::F16x8Sub: | |
| return builder.makeBinary(SubVecF16x8); | |
| case BinaryConsts::F16x8Mul: | |
| return builder.makeBinary(MulVecF16x8); | |
| case BinaryConsts::F16x8Div: | |
| return builder.makeBinary(DivVecF16x8); | |
| case BinaryConsts::F16x8Min: | |
| return builder.makeBinary(MinVecF16x8); | |
| case BinaryConsts::F16x8Max: | |
| return builder.makeBinary(MaxVecF16x8); | |
| case BinaryConsts::F16x8Pmin: | |
| return builder.makeBinary(PMinVecF16x8); | |
| case BinaryConsts::F16x8Pmax: | |
| return builder.makeBinary(PMaxVecF16x8); | |
| case BinaryConsts::F32x4Add: | |
| return builder.makeBinary(AddVecF32x4); | |
| case BinaryConsts::F32x4Sub: | |
| return builder.makeBinary(SubVecF32x4); | |
| case BinaryConsts::F32x4Mul: | |
| return builder.makeBinary(MulVecF32x4); | |
| case BinaryConsts::F32x4Div: | |
| return builder.makeBinary(DivVecF32x4); | |
| case BinaryConsts::F32x4Min: | |
| return builder.makeBinary(MinVecF32x4); | |
| case BinaryConsts::F32x4Max: | |
| return builder.makeBinary(MaxVecF32x4); | |
| case BinaryConsts::F32x4Pmin: | |
| return builder.makeBinary(PMinVecF32x4); | |
| case BinaryConsts::F32x4Pmax: | |
| return builder.makeBinary(PMaxVecF32x4); | |
| case BinaryConsts::F64x2Add: | |
| return builder.makeBinary(AddVecF64x2); | |
| case BinaryConsts::F64x2Sub: | |
| return builder.makeBinary(SubVecF64x2); | |
| case BinaryConsts::F64x2Mul: | |
| return builder.makeBinary(MulVecF64x2); | |
| case BinaryConsts::F64x2Div: | |
| return builder.makeBinary(DivVecF64x2); | |
| case BinaryConsts::F64x2Min: | |
| return builder.makeBinary(MinVecF64x2); | |
| case BinaryConsts::F64x2Max: | |
| return builder.makeBinary(MaxVecF64x2); | |
| case BinaryConsts::F64x2Pmin: | |
| return builder.makeBinary(PMinVecF64x2); | |
| case BinaryConsts::F64x2Pmax: | |
| return builder.makeBinary(PMaxVecF64x2); | |
| case BinaryConsts::I8x16NarrowI16x8S: | |
| return builder.makeBinary(NarrowSVecI16x8ToVecI8x16); | |
| case BinaryConsts::I8x16NarrowI16x8U: | |
| return builder.makeBinary(NarrowUVecI16x8ToVecI8x16); | |
| case BinaryConsts::I16x8NarrowI32x4S: | |
| return builder.makeBinary(NarrowSVecI32x4ToVecI16x8); | |
| case BinaryConsts::I16x8NarrowI32x4U: | |
| return builder.makeBinary(NarrowUVecI32x4ToVecI16x8); | |
| case BinaryConsts::I8x16Swizzle: | |
| return builder.makeBinary(SwizzleVecI8x16); | |
| case BinaryConsts::I8x16RelaxedSwizzle: | |
| return builder.makeBinary(RelaxedSwizzleVecI8x16); | |
| case BinaryConsts::F32x4RelaxedMin: | |
| return builder.makeBinary(RelaxedMinVecF32x4); | |
| case BinaryConsts::F32x4RelaxedMax: | |
| return builder.makeBinary(RelaxedMaxVecF32x4); | |
| case BinaryConsts::F64x2RelaxedMin: | |
| return builder.makeBinary(RelaxedMinVecF64x2); | |
| case BinaryConsts::F64x2RelaxedMax: | |
| return builder.makeBinary(RelaxedMaxVecF64x2); | |
| case BinaryConsts::I16x8RelaxedQ15MulrS: | |
| return builder.makeBinary(RelaxedQ15MulrSVecI16x8); | |
| case BinaryConsts::I16x8DotI8x16I7x16S: | |
| return builder.makeBinary(RelaxedDotI8x16I7x16SToVecI16x8); | |
| case BinaryConsts::I8x16Splat: | |
| return builder.makeUnary(SplatVecI8x16); | |
| case BinaryConsts::I16x8Splat: | |
| return builder.makeUnary(SplatVecI16x8); | |
| case BinaryConsts::I32x4Splat: | |
| return builder.makeUnary(SplatVecI32x4); | |
| case BinaryConsts::I64x2Splat: | |
| return builder.makeUnary(SplatVecI64x2); | |
| case BinaryConsts::F16x8Splat: | |
| return builder.makeUnary(SplatVecF16x8); | |
| case BinaryConsts::F32x4Splat: | |
| return builder.makeUnary(SplatVecF32x4); | |
| case BinaryConsts::F64x2Splat: | |
| return builder.makeUnary(SplatVecF64x2); | |
| case BinaryConsts::V128Not: | |
| return builder.makeUnary(NotVec128); | |
| case BinaryConsts::V128AnyTrue: | |
| return builder.makeUnary(AnyTrueVec128); | |
| case BinaryConsts::I8x16Popcnt: | |
| return builder.makeUnary(PopcntVecI8x16); | |
| case BinaryConsts::I8x16Abs: | |
| return builder.makeUnary(AbsVecI8x16); | |
| case BinaryConsts::I8x16Neg: | |
| return builder.makeUnary(NegVecI8x16); | |
| case BinaryConsts::I8x16AllTrue: | |
| return builder.makeUnary(AllTrueVecI8x16); | |
| case BinaryConsts::I8x16Bitmask: | |
| return builder.makeUnary(BitmaskVecI8x16); | |
| case BinaryConsts::I16x8Abs: | |
| return builder.makeUnary(AbsVecI16x8); | |
| case BinaryConsts::I16x8Neg: | |
| return builder.makeUnary(NegVecI16x8); | |
| case BinaryConsts::I16x8AllTrue: | |
| return builder.makeUnary(AllTrueVecI16x8); | |
| case BinaryConsts::I16x8Bitmask: | |
| return builder.makeUnary(BitmaskVecI16x8); | |
| case BinaryConsts::I32x4Abs: | |
| return builder.makeUnary(AbsVecI32x4); | |
| case BinaryConsts::I32x4Neg: | |
| return builder.makeUnary(NegVecI32x4); | |
| case BinaryConsts::I32x4AllTrue: | |
| return builder.makeUnary(AllTrueVecI32x4); | |
| case BinaryConsts::I32x4Bitmask: | |
| return builder.makeUnary(BitmaskVecI32x4); | |
| case BinaryConsts::I64x2Abs: | |
| return builder.makeUnary(AbsVecI64x2); | |
| case BinaryConsts::I64x2Neg: | |
| return builder.makeUnary(NegVecI64x2); | |
| case BinaryConsts::I64x2AllTrue: | |
| return builder.makeUnary(AllTrueVecI64x2); | |
| case BinaryConsts::I64x2Bitmask: | |
| return builder.makeUnary(BitmaskVecI64x2); | |
| case BinaryConsts::F16x8Abs: | |
| return builder.makeUnary(AbsVecF16x8); | |
| case BinaryConsts::F16x8Neg: | |
| return builder.makeUnary(NegVecF16x8); | |
| case BinaryConsts::F16x8Sqrt: | |
| return builder.makeUnary(SqrtVecF16x8); | |
| case BinaryConsts::F16x8Ceil: | |
| return builder.makeUnary(CeilVecF16x8); | |
| case BinaryConsts::F16x8Floor: | |
| return builder.makeUnary(FloorVecF16x8); | |
| case BinaryConsts::F16x8Trunc: | |
| return builder.makeUnary(TruncVecF16x8); | |
| case BinaryConsts::F16x8Nearest: | |
| return builder.makeUnary(NearestVecF16x8); | |
| case BinaryConsts::F32x4Abs: | |
| return builder.makeUnary(AbsVecF32x4); | |
| case BinaryConsts::F32x4Neg: | |
| return builder.makeUnary(NegVecF32x4); | |
| case BinaryConsts::F32x4Sqrt: | |
| return builder.makeUnary(SqrtVecF32x4); | |
| case BinaryConsts::F32x4Ceil: | |
| return builder.makeUnary(CeilVecF32x4); | |
| case BinaryConsts::F32x4Floor: | |
| return builder.makeUnary(FloorVecF32x4); | |
| case BinaryConsts::F32x4Trunc: | |
| return builder.makeUnary(TruncVecF32x4); | |
| case BinaryConsts::F32x4Nearest: | |
| return builder.makeUnary(NearestVecF32x4); | |
| case BinaryConsts::F64x2Abs: | |
| return builder.makeUnary(AbsVecF64x2); | |
| case BinaryConsts::F64x2Neg: | |
| return builder.makeUnary(NegVecF64x2); | |
| case BinaryConsts::F64x2Sqrt: | |
| return builder.makeUnary(SqrtVecF64x2); | |
| case BinaryConsts::F64x2Ceil: | |
| return builder.makeUnary(CeilVecF64x2); | |
| case BinaryConsts::F64x2Floor: | |
| return builder.makeUnary(FloorVecF64x2); | |
| case BinaryConsts::F64x2Trunc: | |
| return builder.makeUnary(TruncVecF64x2); | |
| case BinaryConsts::F64x2Nearest: | |
| return builder.makeUnary(NearestVecF64x2); | |
| case BinaryConsts::I16x8ExtaddPairwiseI8x16S: | |
| return builder.makeUnary(ExtAddPairwiseSVecI8x16ToI16x8); | |
| case BinaryConsts::I16x8ExtaddPairwiseI8x16U: | |
| return builder.makeUnary(ExtAddPairwiseUVecI8x16ToI16x8); | |
| case BinaryConsts::I32x4ExtaddPairwiseI16x8S: | |
| return builder.makeUnary(ExtAddPairwiseSVecI16x8ToI32x4); | |
| case BinaryConsts::I32x4ExtaddPairwiseI16x8U: | |
| return builder.makeUnary(ExtAddPairwiseUVecI16x8ToI32x4); | |
| case BinaryConsts::I32x4TruncSatF32x4S: | |
| return builder.makeUnary(TruncSatSVecF32x4ToVecI32x4); | |
| case BinaryConsts::I32x4TruncSatF32x4U: | |
| return builder.makeUnary(TruncSatUVecF32x4ToVecI32x4); | |
| case BinaryConsts::F32x4ConvertI32x4S: | |
| return builder.makeUnary(ConvertSVecI32x4ToVecF32x4); | |
| case BinaryConsts::F32x4ConvertI32x4U: | |
| return builder.makeUnary(ConvertUVecI32x4ToVecF32x4); | |
| case BinaryConsts::I16x8ExtendLowI8x16S: | |
| return builder.makeUnary(ExtendLowSVecI8x16ToVecI16x8); | |
| case BinaryConsts::I16x8ExtendHighI8x16S: | |
| return builder.makeUnary(ExtendHighSVecI8x16ToVecI16x8); | |
| case BinaryConsts::I16x8ExtendLowI8x16U: | |
| return builder.makeUnary(ExtendLowUVecI8x16ToVecI16x8); | |
| case BinaryConsts::I16x8ExtendHighI8x16U: | |
| return builder.makeUnary(ExtendHighUVecI8x16ToVecI16x8); | |
| case BinaryConsts::I32x4ExtendLowI16x8S: | |
| return builder.makeUnary(ExtendLowSVecI16x8ToVecI32x4); | |
| case BinaryConsts::I32x4ExtendHighI16x8S: | |
| return builder.makeUnary(ExtendHighSVecI16x8ToVecI32x4); | |
| case BinaryConsts::I32x4ExtendLowI16x8U: | |
| return builder.makeUnary(ExtendLowUVecI16x8ToVecI32x4); | |
| case BinaryConsts::I32x4ExtendHighI16x8U: | |
| return builder.makeUnary(ExtendHighUVecI16x8ToVecI32x4); | |
| case BinaryConsts::I64x2ExtendLowI32x4S: | |
| return builder.makeUnary(ExtendLowSVecI32x4ToVecI64x2); | |
| case BinaryConsts::I64x2ExtendHighI32x4S: | |
| return builder.makeUnary(ExtendHighSVecI32x4ToVecI64x2); | |
| case BinaryConsts::I64x2ExtendLowI32x4U: | |
| return builder.makeUnary(ExtendLowUVecI32x4ToVecI64x2); | |
| case BinaryConsts::I64x2ExtendHighI32x4U: | |
| return builder.makeUnary(ExtendHighUVecI32x4ToVecI64x2); | |
| case BinaryConsts::F64x2ConvertLowI32x4S: | |
| return builder.makeUnary(ConvertLowSVecI32x4ToVecF64x2); | |
| case BinaryConsts::F64x2ConvertLowI32x4U: | |
| return builder.makeUnary(ConvertLowUVecI32x4ToVecF64x2); | |
| case BinaryConsts::I32x4TruncSatF64x2SZero: | |
| return builder.makeUnary(TruncSatZeroSVecF64x2ToVecI32x4); | |
| case BinaryConsts::I32x4TruncSatF64x2UZero: | |
| return builder.makeUnary(TruncSatZeroUVecF64x2ToVecI32x4); | |
| case BinaryConsts::F32x4DemoteF64x2Zero: | |
| return builder.makeUnary(DemoteZeroVecF64x2ToVecF32x4); | |
| case BinaryConsts::F64x2PromoteLowF32x4: | |
| return builder.makeUnary(PromoteLowVecF32x4ToVecF64x2); | |
| case BinaryConsts::I32x4RelaxedTruncF32x4S: | |
| return builder.makeUnary(RelaxedTruncSVecF32x4ToVecI32x4); | |
| case BinaryConsts::I32x4RelaxedTruncF32x4U: | |
| return builder.makeUnary(RelaxedTruncUVecF32x4ToVecI32x4); | |
| case BinaryConsts::I32x4RelaxedTruncF64x2SZero: | |
| return builder.makeUnary(RelaxedTruncZeroSVecF64x2ToVecI32x4); | |
| case BinaryConsts::I32x4RelaxedTruncF64x2UZero: | |
| return builder.makeUnary(RelaxedTruncZeroUVecF64x2ToVecI32x4); | |
| case BinaryConsts::I16x8TruncSatF16x8S: | |
| return builder.makeUnary(TruncSatSVecF16x8ToVecI16x8); | |
| case BinaryConsts::I16x8TruncSatF16x8U: | |
| return builder.makeUnary(TruncSatUVecF16x8ToVecI16x8); | |
| case BinaryConsts::F16x8ConvertI16x8S: | |
| return builder.makeUnary(ConvertSVecI16x8ToVecF16x8); | |
| case BinaryConsts::F16x8ConvertI16x8U: | |
| return builder.makeUnary(ConvertUVecI16x8ToVecF16x8); | |
| case BinaryConsts::F16x8DemoteF32x4Zero: | |
| return builder.makeUnary(DemoteZeroVecF32x4ToVecF16x8); | |
| case BinaryConsts::F16x8DemoteF64x2Zero: | |
| return builder.makeUnary(DemoteZeroVecF64x2ToVecF16x8); | |
| case BinaryConsts::F32x4PromoteLowF16x8: | |
| return builder.makeUnary(PromoteLowVecF16x8ToVecF32x4); | |
| case BinaryConsts::I8x16ExtractLaneS: | |
| return builder.makeSIMDExtract(ExtractLaneSVecI8x16, | |
| getLaneIndex(16)); | |
| case BinaryConsts::I8x16ExtractLaneU: | |
| return builder.makeSIMDExtract(ExtractLaneUVecI8x16, | |
| getLaneIndex(16)); | |
| case BinaryConsts::I16x8ExtractLaneS: | |
| return builder.makeSIMDExtract(ExtractLaneSVecI16x8, getLaneIndex(8)); | |
| case BinaryConsts::I16x8ExtractLaneU: | |
| return builder.makeSIMDExtract(ExtractLaneUVecI16x8, getLaneIndex(8)); | |
| case BinaryConsts::I32x4ExtractLane: | |
| return builder.makeSIMDExtract(ExtractLaneVecI32x4, getLaneIndex(4)); | |
| case BinaryConsts::I64x2ExtractLane: | |
| return builder.makeSIMDExtract(ExtractLaneVecI64x2, getLaneIndex(2)); | |
| case BinaryConsts::F16x8ExtractLane: | |
| return builder.makeSIMDExtract(ExtractLaneVecF16x8, getLaneIndex(8)); | |
| case BinaryConsts::F32x4ExtractLane: | |
| return builder.makeSIMDExtract(ExtractLaneVecF32x4, getLaneIndex(4)); | |
| case BinaryConsts::F64x2ExtractLane: | |
| return builder.makeSIMDExtract(ExtractLaneVecF64x2, getLaneIndex(2)); | |
| case BinaryConsts::I8x16ReplaceLane: | |
| return builder.makeSIMDReplace(ReplaceLaneVecI8x16, getLaneIndex(16)); | |
| case BinaryConsts::I16x8ReplaceLane: | |
| return builder.makeSIMDReplace(ReplaceLaneVecI16x8, getLaneIndex(8)); | |
| case BinaryConsts::I32x4ReplaceLane: | |
| return builder.makeSIMDReplace(ReplaceLaneVecI32x4, getLaneIndex(4)); | |
| case BinaryConsts::I64x2ReplaceLane: | |
| return builder.makeSIMDReplace(ReplaceLaneVecI64x2, getLaneIndex(2)); | |
| case BinaryConsts::F16x8ReplaceLane: | |
| return builder.makeSIMDReplace(ReplaceLaneVecF16x8, getLaneIndex(8)); | |
| case BinaryConsts::F32x4ReplaceLane: | |
| return builder.makeSIMDReplace(ReplaceLaneVecF32x4, getLaneIndex(4)); | |
| case BinaryConsts::F64x2ReplaceLane: | |
| return builder.makeSIMDReplace(ReplaceLaneVecF64x2, getLaneIndex(2)); | |
| case BinaryConsts::I8x16Shuffle: { | |
| std::array<uint8_t, 16> lanes; | |
| for (Index i = 0; i < 16; ++i) { | |
| lanes[i] = getLaneIndex(32); | |
| } | |
| return builder.makeSIMDShuffle(lanes); | |
| } | |
| case BinaryConsts::V128Bitselect: | |
| return builder.makeSIMDTernary(Bitselect); | |
| case BinaryConsts::I8x16Laneselect: | |
| return builder.makeSIMDTernary(RelaxedLaneselectI8x16); | |
| case BinaryConsts::I16x8Laneselect: | |
| return builder.makeSIMDTernary(RelaxedLaneselectI16x8); | |
| case BinaryConsts::I32x4Laneselect: | |
| return builder.makeSIMDTernary(RelaxedLaneselectI32x4); | |
| case BinaryConsts::I64x2Laneselect: | |
| return builder.makeSIMDTernary(RelaxedLaneselectI64x2); | |
| case BinaryConsts::F16x8Madd: | |
| return builder.makeSIMDTernary(MaddVecF16x8); | |
| case BinaryConsts::F16x8Nmadd: | |
| return builder.makeSIMDTernary(NmaddVecF16x8); | |
| case BinaryConsts::F32x4RelaxedMadd: | |
| return builder.makeSIMDTernary(RelaxedMaddVecF32x4); | |
| case BinaryConsts::F32x4RelaxedNmadd: | |
| return builder.makeSIMDTernary(RelaxedNmaddVecF32x4); | |
| case BinaryConsts::F64x2RelaxedMadd: | |
| return builder.makeSIMDTernary(RelaxedMaddVecF64x2); | |
| case BinaryConsts::F64x2RelaxedNmadd: | |
| return builder.makeSIMDTernary(RelaxedNmaddVecF64x2); | |
| case BinaryConsts::I32x4DotI8x16I7x16AddS: | |
| return builder.makeSIMDTernary(RelaxedDotI8x16I7x16AddSToVecI32x4); | |
| case BinaryConsts::I8x16Shl: | |
| return builder.makeSIMDShift(ShlVecI8x16); | |
| case BinaryConsts::I8x16ShrS: | |
| return builder.makeSIMDShift(ShrSVecI8x16); | |
| case BinaryConsts::I8x16ShrU: | |
| return builder.makeSIMDShift(ShrUVecI8x16); | |
| case BinaryConsts::I16x8Shl: | |
| return builder.makeSIMDShift(ShlVecI16x8); | |
| case BinaryConsts::I16x8ShrS: | |
| return builder.makeSIMDShift(ShrSVecI16x8); | |
| case BinaryConsts::I16x8ShrU: | |
| return builder.makeSIMDShift(ShrUVecI16x8); | |
| case BinaryConsts::I32x4Shl: | |
| return builder.makeSIMDShift(ShlVecI32x4); | |
| case BinaryConsts::I32x4ShrS: | |
| return builder.makeSIMDShift(ShrSVecI32x4); | |
| case BinaryConsts::I32x4ShrU: | |
| return builder.makeSIMDShift(ShrUVecI32x4); | |
| case BinaryConsts::I64x2Shl: | |
| return builder.makeSIMDShift(ShlVecI64x2); | |
| case BinaryConsts::I64x2ShrS: | |
| return builder.makeSIMDShift(ShrSVecI64x2); | |
| case BinaryConsts::I64x2ShrU: | |
| return builder.makeSIMDShift(ShrUVecI64x2); | |
| case BinaryConsts::V128Const: | |
| return builder.makeConst(getVec128Literal()); | |
| case BinaryConsts::V128Store: { | |
| return readStore(16, Type::v128); | |
| } | |
| case BinaryConsts::V128Load: { | |
| return readLoad(16, false, Type::v128); | |
| } | |
| case BinaryConsts::V128Load8Splat: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoad(Load8SplatVec128, offset, align, mem); | |
| } | |
| case BinaryConsts::V128Load16Splat: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoad(Load16SplatVec128, offset, align, mem); | |
| } | |
| case BinaryConsts::V128Load32Splat: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoad(Load32SplatVec128, offset, align, mem); | |
| } | |
| case BinaryConsts::V128Load64Splat: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoad(Load64SplatVec128, offset, align, mem); | |
| } | |
| case BinaryConsts::V128Load8x8S: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoad(Load8x8SVec128, offset, align, mem); | |
| } | |
| case BinaryConsts::V128Load8x8U: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoad(Load8x8UVec128, offset, align, mem); | |
| } | |
| case BinaryConsts::V128Load16x4S: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoad(Load16x4SVec128, offset, align, mem); | |
| } | |
| case BinaryConsts::V128Load16x4U: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoad(Load16x4UVec128, offset, align, mem); | |
| } | |
| case BinaryConsts::V128Load32x2S: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoad(Load32x2SVec128, offset, align, mem); | |
| } | |
| case BinaryConsts::V128Load32x2U: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoad(Load32x2UVec128, offset, align, mem); | |
| } | |
| case BinaryConsts::V128Load32Zero: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoad(Load32ZeroVec128, offset, align, mem); | |
| } | |
| case BinaryConsts::V128Load64Zero: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoad(Load64ZeroVec128, offset, align, mem); | |
| } | |
| case BinaryConsts::V128Load8Lane: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoadStoreLane( | |
| Load8LaneVec128, offset, align, getLaneIndex(16), mem); | |
| } | |
| case BinaryConsts::V128Load16Lane: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoadStoreLane( | |
| Load16LaneVec128, offset, align, getLaneIndex(8), mem); | |
| } | |
| case BinaryConsts::V128Load32Lane: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoadStoreLane( | |
| Load32LaneVec128, offset, align, getLaneIndex(4), mem); | |
| } | |
| case BinaryConsts::V128Load64Lane: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoadStoreLane( | |
| Load64LaneVec128, offset, align, getLaneIndex(2), mem); | |
| } | |
| case BinaryConsts::V128Store8Lane: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoadStoreLane( | |
| Store8LaneVec128, offset, align, getLaneIndex(16), mem); | |
| } | |
| case BinaryConsts::V128Store16Lane: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoadStoreLane( | |
| Store16LaneVec128, offset, align, getLaneIndex(8), mem); | |
| } | |
| case BinaryConsts::V128Store32Lane: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoadStoreLane( | |
| Store32LaneVec128, offset, align, getLaneIndex(4), mem); | |
| } | |
| case BinaryConsts::V128Store64Lane: { | |
| auto [mem, align, offset, backing] = getMemarg(); | |
| return builder.makeSIMDLoadStoreLane( | |
| Store64LaneVec128, offset, align, getLaneIndex(2), mem); | |
| } | |
| } | |
| return Err{"unknown SIMD operation " + std::to_string(op)}; | |
| } | |
| case BinaryConsts::GCPrefix: { | |
| auto op = getU32LEB(); | |
| switch (op) { | |
| case BinaryConsts::RefI31: | |
| return builder.makeRefI31(Unshared); | |
| case BinaryConsts::RefI31Shared: | |
| return builder.makeRefI31(Shared); | |
| case BinaryConsts::I31GetS: | |
| return builder.makeI31Get(true); | |
| case BinaryConsts::I31GetU: | |
| return builder.makeI31Get(false); | |
| case BinaryConsts::RefTest: { | |
| auto [heapType, exactness] = getHeapType(); | |
| return builder.makeRefTest(Type(heapType, NonNullable, exactness)); | |
| } | |
| case BinaryConsts::RefTestNull: { | |
| auto [heapType, exactness] = getHeapType(); | |
| return builder.makeRefTest(Type(heapType, Nullable, exactness)); | |
| } | |
| case BinaryConsts::RefCast: { | |
| auto [heapType, exactness] = getHeapType(); | |
| return builder.makeRefCast(Type(heapType, NonNullable, exactness), | |
| false); | |
| } | |
| case BinaryConsts::RefCastNull: { | |
| auto [heapType, exactness] = getHeapType(); | |
| return builder.makeRefCast(Type(heapType, Nullable, exactness), | |
| false); | |
| } | |
| case BinaryConsts::RefCastDescEq: { | |
| auto [heapType, exactness] = getHeapType(); | |
| return builder.makeRefCast(Type(heapType, NonNullable, exactness), | |
| true); | |
| } | |
| case BinaryConsts::RefCastDescEqNull: { | |
| auto [heapType, exactness] = getHeapType(); | |
| return builder.makeRefCast(Type(heapType, Nullable, exactness), true); | |
| } | |
| case BinaryConsts::RefGetDesc: { | |
| auto type = getIndexedHeapType(); | |
| return builder.makeRefGetDesc(type); | |
| } | |
| case BinaryConsts::BrOnCast: | |
| case BinaryConsts::BrOnCastFail: | |
| case BinaryConsts::BrOnCastDescEq: | |
| case BinaryConsts::BrOnCastDescEqFail: { | |
| auto flags = getInt8(); | |
| auto srcNull = (flags & BinaryConsts::BrOnCastFlag::InputNullable) | |
| ? Nullable | |
| : NonNullable; | |
| auto dstNull = (flags & BinaryConsts::BrOnCastFlag::OutputNullable) | |
| ? Nullable | |
| : NonNullable; | |
| auto label = getU32LEB(); | |
| auto [srcType, srcExact] = getHeapType(); | |
| auto [dstType, dstExact] = getHeapType(); | |
| auto in = Type(srcType, srcNull, srcExact); | |
| auto cast = Type(dstType, dstNull, dstExact); | |
| auto kind = op == BinaryConsts::BrOnCast ? BrOnCast | |
| : op == BinaryConsts::BrOnCastFail ? BrOnCastFail | |
| : op == BinaryConsts::BrOnCastDescEq ? BrOnCastDescEq | |
| : BrOnCastDescEqFail; | |
| return builder.makeBrOn(label, kind, in, cast); | |
| } | |
| case BinaryConsts::StructNew: | |
| case BinaryConsts::StructNewDesc: { | |
| bool isDesc = op == BinaryConsts::StructNewDesc; | |
| return builder.makeStructNew(getIndexedHeapType(), isDesc); | |
| } | |
| case BinaryConsts::StructNewDefault: | |
| case BinaryConsts::StructNewDefaultDesc: { | |
| bool isDesc = op == BinaryConsts::StructNewDefaultDesc; | |
| return builder.makeStructNewDefault(getIndexedHeapType(), isDesc); | |
| } | |
| case BinaryConsts::StructGet: | |
| case BinaryConsts::StructGetS: | |
| case BinaryConsts::StructGetU: { | |
| auto type = getIndexedHeapType(); | |
| auto field = getU32LEB(); | |
| return builder.makeStructGet(type, | |
| field, | |
| op == BinaryConsts::StructGetS, | |
| MemoryOrder::Unordered); | |
| } | |
| case BinaryConsts::StructSet: { | |
| auto type = getIndexedHeapType(); | |
| auto field = getU32LEB(); | |
| return builder.makeStructSet(type, field, MemoryOrder::Unordered); | |
| } | |
| case BinaryConsts::ArrayNew: | |
| return builder.makeArrayNew(getIndexedHeapType()); | |
| case BinaryConsts::ArrayNewDefault: | |
| return builder.makeArrayNewDefault(getIndexedHeapType()); | |
| case BinaryConsts::ArrayNewFixed: { | |
| auto type = getIndexedHeapType(); | |
| auto arity = getU32LEB(); | |
| return builder.makeArrayNewFixed(type, arity); | |
| } | |
| case BinaryConsts::ArrayNewData: { | |
| auto type = getIndexedHeapType(); | |
| auto data = getDataName(getU32LEB()); | |
| return builder.makeArrayNewData(type, data); | |
| } | |
| case BinaryConsts::ArrayNewElem: { | |
| auto type = getIndexedHeapType(); | |
| auto elem = getElemName(getU32LEB()); | |
| return builder.makeArrayNewElem(type, elem); | |
| } | |
| case BinaryConsts::ArrayGet: | |
| case BinaryConsts::ArrayGetU: | |
| return builder.makeArrayGet( | |
| getIndexedHeapType(), false, MemoryOrder::Unordered); | |
| case BinaryConsts::ArrayGetS: | |
| return builder.makeArrayGet( | |
| getIndexedHeapType(), true, MemoryOrder::Unordered); | |
| case BinaryConsts::ArraySet: | |
| return builder.makeArraySet(getIndexedHeapType(), | |
| MemoryOrder::Unordered); | |
| case BinaryConsts::ArrayLen: | |
| return builder.makeArrayLen(); | |
| case BinaryConsts::ArrayCopy: { | |
| auto dest = getIndexedHeapType(); | |
| auto src = getIndexedHeapType(); | |
| return builder.makeArrayCopy(dest, src); | |
| } | |
| case BinaryConsts::ArrayFill: | |
| return builder.makeArrayFill(getIndexedHeapType()); | |
| case BinaryConsts::ArrayInitData: { | |
| auto type = getIndexedHeapType(); | |
| auto data = getDataName(getU32LEB()); | |
| return builder.makeArrayInitData(type, data); | |
| } | |
| case BinaryConsts::ArrayInitElem: { | |
| auto type = getIndexedHeapType(); | |
| auto elem = getElemName(getU32LEB()); | |
| return builder.makeArrayInitElem(type, elem); | |
| } | |
| case BinaryConsts::StringNewLossyUTF8Array: | |
| return builder.makeStringNew(StringNewLossyUTF8Array); | |
| case BinaryConsts::StringNewWTF16Array: | |
| return builder.makeStringNew(StringNewWTF16Array); | |
| case BinaryConsts::StringFromCodePoint: | |
| return builder.makeStringNew(StringNewFromCodePoint); | |
| case BinaryConsts::StringAsWTF16: | |
| // This turns into nothing because we do not represent stringviews in | |
| // the IR. | |
| return Ok{}; | |
| case BinaryConsts::StringConst: | |
| return builder.makeStringConst(getIndexedString()); | |
| case BinaryConsts::StringMeasureUTF8: | |
| return builder.makeStringMeasure(StringMeasureUTF8); | |
| case BinaryConsts::StringMeasureWTF16: | |
| return builder.makeStringMeasure(StringMeasureWTF16); | |
| case BinaryConsts::StringEncodeLossyUTF8Array: | |
| return builder.makeStringEncode(StringEncodeLossyUTF8Array); | |
| case BinaryConsts::StringEncodeWTF16Array: | |
| return builder.makeStringEncode(StringEncodeWTF16Array); | |
| case BinaryConsts::StringConcat: | |
| return builder.makeStringConcat(); | |
| case BinaryConsts::StringEq: | |
| return builder.makeStringEq(StringEqEqual); | |
| case BinaryConsts::StringTest: | |
| return builder.makeStringTest(); | |
| case BinaryConsts::StringCompare: | |
| return builder.makeStringEq(StringEqCompare); | |
| case BinaryConsts::StringViewWTF16GetCodePoint: | |
| return builder.makeStringWTF16Get(); | |
| case BinaryConsts::StringViewWTF16Slice: | |
| return builder.makeStringSliceWTF(); | |
| case BinaryConsts::AnyConvertExtern: | |
| return builder.makeRefAs(AnyConvertExtern); | |
| case BinaryConsts::ExternConvertAny: | |
| return builder.makeRefAs(ExternConvertAny); | |
| } | |
| return Err{"unknown GC operation " + std::to_string(op)}; | |
| } | |
| } | |
| return Err{"unknown operation " + std::to_string(code)}; | |
| } | |
| void WasmBinaryReader::readExports() { | |
| size_t num = getU32LEB(); | |
| std::unordered_set<Name> names; | |
| for (size_t i = 0; i < num; i++) { | |
| Name name = getInlineString(); | |
| if (!names.emplace(name).second) { | |
| throwError("duplicate export name"); | |
| } | |
| auto kind = getU32LEB(); | |
| std::optional<std::variant<Name, HeapType>> value; | |
| auto index = getU32LEB(); | |
| switch (kind) { | |
| case ExternalKind::Function: | |
| value = getFunctionName(index); | |
| break; | |
| case ExternalKind::Table: | |
| value = getTableName(index); | |
| break; | |
| case ExternalKind::Memory: | |
| value = getMemoryName(index); | |
| break; | |
| case ExternalKind::Global: | |
| value = getGlobalName(index); | |
| break; | |
| case ExternalKind::Tag: | |
| value = getTagName(index); | |
| break; | |
| case ExternalKind::Invalid: | |
| break; | |
| } | |
| if (!value) { | |
| throwError("invalid export kind"); | |
| } | |
| wasm.addExport(new Export(name, ExternalKind(kind), *value)); | |
| } | |
| } | |
| Expression* WasmBinaryReader::readExpression() { | |
| assert(builder.empty()); | |
| while (more() && input[pos] != BinaryConsts::End) { | |
| auto inst = readInst(); | |
| if (auto* err = inst.getErr()) { | |
| throwError(err->msg); | |
| } | |
| } | |
| if (!more()) { | |
| throwError("unexpected end of input"); | |
| } | |
| ++pos; | |
| auto expr = builder.build(); | |
| if (auto* err = expr.getErr()) { | |
| throwError(err->msg); | |
| } | |
| return *expr; | |
| } | |
| void WasmBinaryReader::readStrings() { | |
| auto reserved = getU32LEB(); | |
| if (reserved != 0) { | |
| throwError("unexpected reserved value in strings"); | |
| } | |
| size_t num = getU32LEB(); | |
| for (size_t i = 0; i < num; i++) { | |
| auto string = getInlineString(false); | |
| // Re-encode from WTF-8 to WTF-16. | |
| std::stringstream wtf16; | |
| if (!String::convertWTF8ToWTF16(wtf16, string.view())) { | |
| throwError("invalid string constant"); | |
| } | |
| // TODO: Use wtf16.view() once we have C++20. | |
| strings.push_back(wtf16.str()); | |
| } | |
| } | |
| Name WasmBinaryReader::getIndexedString() { | |
| auto index = getU32LEB(); | |
| if (index >= strings.size()) { | |
| throwError("bad string index"); | |
| } | |
| return strings[index]; | |
| } | |
| void WasmBinaryReader::readGlobals() { | |
| size_t num = getU32LEB(); | |
| auto numImports = wasm.globals.size(); | |
| for (auto& [index, name] : globalNames) { | |
| if (index >= num + numImports) { | |
| std::cerr << "warning: global index out of bounds in name section: " | |
| << name << " at index " << index << '\n'; | |
| } | |
| } | |
| for (size_t i = 0; i < num; i++) { | |
| auto [name, isExplicit] = getOrMakeName( | |
| globalNames, numImports + i, makeName("global$", i), usedGlobalNames); | |
| auto type = getConcreteType(); | |
| auto mutable_ = getU32LEB(); | |
| if (mutable_ & ~1) { | |
| throwError("Global mutability must be 0 or 1"); | |
| } | |
| auto* init = readExpression(); | |
| auto global = Builder::makeGlobal( | |
| name, type, init, mutable_ ? Builder::Mutable : Builder::Immutable); | |
| global->hasExplicitName = isExplicit; | |
| wasm.addGlobal(std::move(global)); | |
| } | |
| } | |
| void WasmBinaryReader::validateBinary() { | |
| if (hasDataCount && wasm.dataSegments.size() != dataCount) { | |
| throwError("Number of segments does not agree with DataCount section"); | |
| } | |
| if (functionTypes.size() != numFuncImports + numFuncBodies) { | |
| throwError("function and code sections have inconsistent lengths"); | |
| } | |
| } | |
| void WasmBinaryReader::createDataSegments(Index count) { | |
| std::unordered_set<Name> usedNames; | |
| for (auto& [index, name] : dataNames) { | |
| if (index >= count) { | |
| std::cerr << "warning: data index out of bounds in name section: " << name | |
| << " at index " << index << '\n'; | |
| } | |
| usedNames.insert(name); | |
| } | |
| for (size_t i = 0; i < count; ++i) { | |
| auto [name, isExplicit] = | |
| getOrMakeName(dataNames, i, makeName("", i), usedNames); | |
| auto curr = Builder::makeDataSegment(name); | |
| curr->hasExplicitName = isExplicit; | |
| wasm.addDataSegment(std::move(curr)); | |
| } | |
| } | |
| void WasmBinaryReader::readDataSegmentCount() { | |
| hasDataCount = true; | |
| dataCount = getU32LEB(); | |
| // Eagerly create the data segments so they are available during parsing of | |
| // the code section. | |
| createDataSegments(dataCount); | |
| } | |
| void WasmBinaryReader::readDataSegments() { | |
| auto num = getU32LEB(); | |
| if (hasDataCount) { | |
| if (num != dataCount) { | |
| throwError("data count and data sections disagree on size"); | |
| } | |
| } else { | |
| // We haven't already created the data segments, so create them now. | |
| createDataSegments(num); | |
| } | |
| assert(wasm.dataSegments.size() == num); | |
| for (size_t i = 0; i < num; i++) { | |
| auto& curr = wasm.dataSegments[i]; | |
| uint32_t flags = getU32LEB(); | |
| if (flags > 2) { | |
| throwError("bad segment flags, must be 0, 1, or 2, not " + | |
| std::to_string(flags)); | |
| } | |
| if (flags & BinaryConsts::IsPassive) { | |
| curr->memory = Name(); | |
| curr->offset = nullptr; | |
| } else { | |
| Index memIdx = 0; | |
| if (flags & BinaryConsts::HasIndex) { | |
| memIdx = getU32LEB(); | |
| } | |
| curr->memory = getMemoryName(memIdx); | |
| curr->offset = readExpression(); | |
| } | |
| auto size = getU32LEB(); | |
| auto data = getByteView(size); | |
| curr->data = {data.begin(), data.end()}; | |
| } | |
| } | |
| void WasmBinaryReader::readTableDeclarations() { | |
| auto num = getU32LEB(); | |
| auto numImports = wasm.tables.size(); | |
| for (auto& [index, name] : tableNames) { | |
| if (index >= num + numImports) { | |
| std::cerr << "warning: table index out of bounds in name section: " | |
| << name << " at index " << index << '\n'; | |
| } | |
| } | |
| for (size_t i = 0; i < num; i++) { | |
| auto [name, isExplicit] = getOrMakeName( | |
| tableNames, numImports + i, makeName("", i), usedTableNames); | |
| bool hasInit = false; | |
| if (peekInt8() == BinaryConsts::HasTableInitializer) { | |
| // Skip past the peeked byte. | |
| getInt8(); | |
| auto reservedByte = getInt8(); | |
| if (reservedByte != BinaryConsts::TableReservedByte) { | |
| // Byte reserved for future extension, must be zero for now. | |
| throwError("Malformed table"); | |
| } | |
| hasInit = true; | |
| } | |
| auto elemType = getType(); | |
| if (!elemType.isRef()) { | |
| throwError("Table type must be a reference type"); | |
| } | |
| auto table = Builder::makeTable(name, elemType); | |
| table->hasExplicitName = isExplicit; | |
| bool is_shared; | |
| uint8_t pageSize = 0xff; | |
| getResizableLimits(table->initial, | |
| table->max, | |
| is_shared, | |
| table->addressType, | |
| pageSize, | |
| Table::kUnlimitedSize); | |
| if (is_shared) { | |
| throwError("Tables may not be shared"); | |
| } | |
| if (pageSize != 0xff) { | |
| throwError("Tables may not specify a custom page size"); | |
| } | |
| if (hasInit) { | |
| auto* init = readExpression(); | |
| table->init = init; | |
| } | |
| wasm.addTable(std::move(table)); | |
| } | |
| } | |
| void WasmBinaryReader::readElementSegments() { | |
| auto num = getU32LEB(); | |
| if (num >= Table::kMaxSize) { | |
| throwError("Too many segments"); | |
| } | |
| std::unordered_set<Name> usedNames; | |
| for (auto& [index, name] : elemNames) { | |
| if (index >= num) { | |
| std::cerr << "warning: elem index out of bounds in name section: " << name | |
| << " at index " << index << '\n'; | |
| } | |
| usedNames.insert(name); | |
| } | |
| for (size_t i = 0; i < num; i++) { | |
| auto [name, isExplicit] = | |
| getOrMakeName(elemNames, i, makeName("", i), usedNames); | |
| auto flags = getU32LEB(); | |
| bool isPassive = (flags & BinaryConsts::IsPassive) != 0; | |
| bool hasTableIdx = !isPassive && ((flags & BinaryConsts::HasIndex) != 0); | |
| bool isDeclarative = | |
| isPassive && ((flags & BinaryConsts::IsDeclarative) != 0); | |
| bool usesExpressions = (flags & BinaryConsts::UsesExpressions) != 0; | |
| if (isDeclarative) { | |
| // Declared segments are needed in wasm text and binary, but not in | |
| // Binaryen IR; skip over the segment. | |
| if (usesExpressions) { | |
| [[maybe_unused]] auto type = getType(); | |
| } else { | |
| auto elemKind = getU32LEB(); | |
| if (elemKind != 0x0) { | |
| throwError("unexpected passive segment elemkind, expected 0, got " + std::to_string(elemKind)); | |
| } | |
| } | |
| auto num = getU32LEB(); | |
| for (Index i = 0; i < num; i++) { | |
| if (usesExpressions) { | |
| readExpression(); | |
| } else { | |
| getU32LEB(); | |
| } | |
| } | |
| continue; | |
| } | |
| auto segment = std::make_unique<ElementSegment>(); | |
| segment->setName(name, isExplicit); | |
| if (!isPassive) { | |
| Index tableIdx = 0; | |
| if (hasTableIdx) { | |
| tableIdx = getU32LEB(); | |
| } | |
| if (tableIdx >= wasm.tables.size()) { | |
| throwError("Table index out of range."); | |
| } | |
| auto* table = wasm.tables[tableIdx].get(); | |
| segment->table = table->name; | |
| segment->offset = readExpression(); | |
| } | |
| if (isPassive || hasTableIdx) { | |
| if (usesExpressions) { | |
| segment->type = getType(); | |
| } else { | |
| auto elemKind = getU32LEB(); | |
| if (elemKind != 0x0) { | |
| throwError("unexpected passive segment elemkind, expected 0, got " + std::to_string(elemKind)); | |
| } | |
| } | |
| } | |
| auto& segmentData = segment->data; | |
| auto size = getU32LEB(); | |
| if (usesExpressions) { | |
| for (Index j = 0; j < size; j++) { | |
| segmentData.push_back(readExpression()); | |
| } | |
| } else { | |
| for (Index j = 0; j < size; j++) { | |
| Index index = getU32LEB(); | |
| auto* refFunc = Builder(wasm).makeRefFunc(getFunctionName(index)); | |
| segmentData.push_back(refFunc); | |
| } | |
| } | |
| wasm.addElementSegment(std::move(segment)); | |
| } | |
| } | |
| void WasmBinaryReader::readTags() { | |
| size_t num = getU32LEB(); | |
| auto numImports = wasm.tags.size(); | |
| for (auto& [index, name] : tagNames) { | |
| if (index >= num + numImports) { | |
| std::cerr << "warning: tag index out of bounds in name section: " << name | |
| << " at index " << index << '\n'; | |
| } | |
| } | |
| for (size_t i = 0; i < num; i++) { | |
| getInt8(); // Reserved 'attribute' field | |
| auto [name, isExplicit] = getOrMakeName( | |
| tagNames, numImports + i, makeName("tag$", i), usedTagNames); | |
| auto typeIndex = getU32LEB(); | |
| auto tag = Builder::makeTag(name, getSignatureByTypeIndex(typeIndex)); | |
| tag->hasExplicitName = isExplicit; | |
| wasm.addTag(std::move(tag)); | |
| } | |
| } | |
| namespace { | |
| // Performs necessary processing of names from the name section before using | |
| // them. Specifically it deduplicates them. | |
| class NameProcessor { | |
| public: | |
| // Returns a unique name. Notes that name for the items to follow to | |
| // keep them unique as well. | |
| Name process(Name name) { | |
| name = Names::getValidNameGivenExisting(name, usedNames); | |
| usedNames.insert(name); | |
| return name; | |
| } | |
| private: | |
| std::unordered_set<Name> usedNames; | |
| }; | |
| } // anonymous namespace | |
| void WasmBinaryReader::readNames(size_t sectionPos, size_t payloadLen) { | |
| // Read the names. | |
| uint32_t lastType = 0; | |
| while (pos < sectionPos + payloadLen) { | |
| auto nameType = getU32LEB(); | |
| if (lastType && nameType <= lastType) { | |
| std::cerr << "warning: out-of-order name subsection: " << nameType | |
| << std::endl; | |
| } | |
| lastType = nameType; | |
| auto subsectionSize = getU32LEB(); | |
| auto subsectionPos = pos; | |
| using Subsection = BinaryConsts::CustomSections::Subsection; | |
| if (nameType == Subsection::NameModule) { | |
| wasm.name = getInlineString(); | |
| } else if (nameType == Subsection::NameFunction) { | |
| auto num = getU32LEB(); | |
| NameProcessor processor; | |
| for (size_t i = 0; i < num; i++) { | |
| auto index = getU32LEB(); | |
| auto rawName = getInlineString(); | |
| auto name = processor.process(rawName); | |
| functionNames[index] = name; | |
| usedFunctionNames.insert(name); | |
| } | |
| } else if (nameType == Subsection::NameLocal) { | |
| auto numFuncs = getU32LEB(); | |
| for (size_t i = 0; i < numFuncs; i++) { | |
| auto funcIndex = getU32LEB(); | |
| auto numLocals = getU32LEB(); | |
| NameProcessor processor; | |
| for (size_t j = 0; j < numLocals; j++) { | |
| auto localIndex = getU32LEB(); | |
| auto rawName = getInlineString(); | |
| auto name = processor.process(rawName); | |
| localNames[funcIndex][localIndex] = name; | |
| } | |
| } | |
| } else if (nameType == Subsection::NameLabel) { | |
| auto numFuncs = getU32LEB(); | |
| for (size_t i = 0; i < numFuncs; i++) { | |
| auto funcIndex = getU32LEB(); | |
| auto numLabels = getU32LEB(); | |
| NameProcessor processor; | |
| for (size_t j = 0; j < numLabels; j++) { | |
| auto labelIndex = getU32LEB(); | |
| auto rawName = getInlineString(); | |
| auto name = processor.process(rawName); | |
| labelNames[funcIndex][labelIndex] = name; | |
| } | |
| } | |
| } else if (nameType == Subsection::NameType) { | |
| auto num = getU32LEB(); | |
| NameProcessor processor; | |
| for (size_t i = 0; i < num; i++) { | |
| auto index = getU32LEB(); | |
| auto rawName = getInlineString(); | |
| auto name = processor.process(rawName); | |
| typeNames[index] = name; | |
| } | |
| } else if (nameType == Subsection::NameTable) { | |
| auto num = getU32LEB(); | |
| NameProcessor processor; | |
| for (size_t i = 0; i < num; i++) { | |
| auto index = getU32LEB(); | |
| auto rawName = getInlineString(); | |
| auto name = processor.process(rawName); | |
| tableNames[index] = name; | |
| usedTableNames.insert(name); | |
| } | |
| } else if (nameType == Subsection::NameElem) { | |
| auto num = getU32LEB(); | |
| NameProcessor processor; | |
| for (size_t i = 0; i < num; i++) { | |
| auto index = getU32LEB(); | |
| auto rawName = getInlineString(); | |
| auto name = processor.process(rawName); | |
| elemNames[index] = name; | |
| } | |
| } else if (nameType == Subsection::NameMemory) { | |
| auto num = getU32LEB(); | |
| NameProcessor processor; | |
| for (size_t i = 0; i < num; i++) { | |
| auto index = getU32LEB(); | |
| auto rawName = getInlineString(); | |
| auto name = processor.process(rawName); | |
| memoryNames[index] = name; | |
| usedMemoryNames.insert(name); | |
| } | |
| } else if (nameType == Subsection::NameData) { | |
| auto num = getU32LEB(); | |
| NameProcessor processor; | |
| for (size_t i = 0; i < num; i++) { | |
| auto index = getU32LEB(); | |
| auto rawName = getInlineString(); | |
| auto name = processor.process(rawName); | |
| dataNames[index] = name; | |
| } | |
| } else if (nameType == Subsection::NameGlobal) { | |
| auto num = getU32LEB(); | |
| NameProcessor processor; | |
| for (size_t i = 0; i < num; i++) { | |
| auto index = getU32LEB(); | |
| auto rawName = getInlineString(); | |
| auto name = processor.process(rawName); | |
| globalNames[index] = name; | |
| usedGlobalNames.insert(name); | |
| } | |
| } else if (nameType == Subsection::NameField) { | |
| auto numTypes = getU32LEB(); | |
| for (size_t i = 0; i < numTypes; i++) { | |
| auto typeIndex = getU32LEB(); | |
| auto numFields = getU32LEB(); | |
| NameProcessor processor; | |
| for (size_t i = 0; i < numFields; i++) { | |
| auto fieldIndex = getU32LEB(); | |
| auto rawName = getInlineString(); | |
| auto name = processor.process(rawName); | |
| fieldNames[typeIndex][fieldIndex] = name; | |
| } | |
| } | |
| } else if (nameType == Subsection::NameTag) { | |
| auto num = getU32LEB(); | |
| NameProcessor processor; | |
| for (size_t i = 0; i < num; i++) { | |
| auto index = getU32LEB(); | |
| auto rawName = getInlineString(); | |
| auto name = processor.process(rawName); | |
| tagNames[index] = name; | |
| usedTagNames.insert(name); | |
| } | |
| } else { | |
| std::cerr << "warning: unknown name subsection with id " | |
| << std::to_string(nameType) << " at " << pos << std::endl; | |
| pos = subsectionPos + subsectionSize; | |
| } | |
| if (pos != subsectionPos + subsectionSize) { | |
| throwError("bad names subsection position change"); | |
| } | |
| } | |
| if (pos != sectionPos + payloadLen) { | |
| throwError("bad names section position change"); | |
| } | |
| } | |
| void WasmBinaryReader::readFeatures(size_t sectionPos, size_t payloadLen) { | |
| wasm.hasFeaturesSection = true; | |
| size_t numFeatures = getU32LEB(); | |
| for (size_t i = 0; i < numFeatures; ++i) { | |
| uint8_t prefix = getInt8(); | |
| bool disallowed = prefix == BinaryConsts::FeatureDisallowed; | |
| bool used = prefix == BinaryConsts::FeatureUsed; | |
| if (!disallowed && !used) { | |
| throwError("Unrecognized feature policy prefix"); | |
| } | |
| Name name = getInlineString(); | |
| if (pos > sectionPos + payloadLen) { | |
| throwError("ill-formed string extends beyond section"); | |
| } | |
| FeatureSet feature; | |
| if (name == BinaryConsts::CustomSections::AtomicsFeature) { | |
| feature = FeatureSet::Atomics; | |
| } else if (name == BinaryConsts::CustomSections::BulkMemoryFeature) { | |
| feature = FeatureSet::BulkMemory; | |
| if (used) { | |
| // For backward compatibility, enable this dependent feature. | |
| feature |= FeatureSet::BulkMemoryOpt; | |
| } | |
| } else if (name == BinaryConsts::CustomSections::BulkMemoryOptFeature) { | |
| feature = FeatureSet::BulkMemoryOpt; | |
| } else if (name == | |
| BinaryConsts::CustomSections::CallIndirectOverlongFeature) { | |
| feature = FeatureSet::CallIndirectOverlong; | |
| } else if (name == BinaryConsts::CustomSections::ExceptionHandlingFeature) { | |
| feature = FeatureSet::ExceptionHandling; | |
| } else if (name == BinaryConsts::CustomSections::MutableGlobalsFeature) { | |
| feature = FeatureSet::MutableGlobals; | |
| } else if (name == BinaryConsts::CustomSections::TruncSatFeature) { | |
| feature = FeatureSet::TruncSat; | |
| } else if (name == BinaryConsts::CustomSections::SignExtFeature) { | |
| feature = FeatureSet::SignExt; | |
| } else if (name == BinaryConsts::CustomSections::SIMD128Feature) { | |
| feature = FeatureSet::SIMD; | |
| } else if (name == BinaryConsts::CustomSections::TailCallFeature) { | |
| feature = FeatureSet::TailCall; | |
| } else if (name == BinaryConsts::CustomSections::ReferenceTypesFeature) { | |
| feature = FeatureSet::ReferenceTypes; | |
| } else if (name == BinaryConsts::CustomSections::MultivalueFeature) { | |
| feature = FeatureSet::Multivalue; | |
| } else if (name == BinaryConsts::CustomSections::GCFeature) { | |
| feature = FeatureSet::GC; | |
| } else if (name == BinaryConsts::CustomSections::Memory64Feature) { | |
| feature = FeatureSet::Memory64; | |
| } else if (name == BinaryConsts::CustomSections::RelaxedSIMDFeature) { | |
| feature = FeatureSet::RelaxedSIMD; | |
| } else if (name == BinaryConsts::CustomSections::ExtendedConstFeature) { | |
| feature = FeatureSet::ExtendedConst; | |
| } else if (name == BinaryConsts::CustomSections::StringsFeature) { | |
| feature = FeatureSet::Strings; | |
| } else if (name == BinaryConsts::CustomSections::MultiMemoryFeature) { | |
| feature = FeatureSet::MultiMemory; | |
| } else if (name == BinaryConsts::CustomSections::StackSwitchingFeature) { | |
| feature = FeatureSet::StackSwitching; | |
| } else if (name == BinaryConsts::CustomSections::SharedEverythingFeature) { | |
| feature = FeatureSet::SharedEverything; | |
| } else if (name == BinaryConsts::CustomSections::FP16Feature) { | |
| feature = FeatureSet::FP16; | |
| } else if (name == BinaryConsts::CustomSections::CustomDescriptorsFeature) { | |
| feature = FeatureSet::CustomDescriptors; | |
| } else if (name == | |
| BinaryConsts::CustomSections::AcquireReleaseAtomicsFeature) { | |
| feature = FeatureSet::AcquireReleaseAtomics; | |
| } else if (name == BinaryConsts::CustomSections::CustomPageSizesFeature) { | |
| feature = FeatureSet::CustomPageSizes; | |
| } else if (name == BinaryConsts::CustomSections::WideArithmeticFeature) { | |
| feature = FeatureSet::WideArithmetic; | |
| } else if (name == BinaryConsts::CustomSections::CompactImportsFeature) { | |
| feature = FeatureSet::CompactImports; | |
| } else if (name == BinaryConsts::CustomSections::RelaxedAtomicsFeature) { | |
| feature = FeatureSet::RelaxedAtomics; | |
| } else { | |
| // Silently ignore unknown features (this may be and old binaryen running | |
| // on a new wasm). | |
| } | |
| if (disallowed && wasm.features.has(feature)) { | |
| std::cerr | |
| << "warning: feature " << feature.toString() | |
| << " was enabled by the user, but disallowed in the features section."; | |
| } | |
| if (used) { | |
| featuresSectionFeatures.enable(feature); | |
| } | |
| } | |
| if (pos != sectionPos + payloadLen) { | |
| throwError("bad features section size"); | |
| } | |
| wasm.features.enable(featuresSectionFeatures); | |
| } | |
| void WasmBinaryReader::readDylink(size_t payloadLen) { | |
| wasm.dylinkSection = std::make_unique<DylinkSection>(); | |
| auto sectionPos = pos; | |
| wasm.dylinkSection->isLegacy = true; | |
| wasm.dylinkSection->memorySize = getU32LEB(); | |
| wasm.dylinkSection->memoryAlignment = getU32LEB(); | |
| wasm.dylinkSection->tableSize = getU32LEB(); | |
| wasm.dylinkSection->tableAlignment = getU32LEB(); | |
| size_t numNeededDynlibs = getU32LEB(); | |
| for (size_t i = 0; i < numNeededDynlibs; ++i) { | |
| wasm.dylinkSection->neededDynlibs.push_back(getInlineString()); | |
| } | |
| if (pos != sectionPos + payloadLen) { | |
| throwError("bad dylink section size"); | |
| } | |
| } | |
| void WasmBinaryReader::readDylink0(size_t payloadLen) { | |
| auto sectionPos = pos; | |
| uint32_t lastType = 0; | |
| wasm.dylinkSection = std::make_unique<DylinkSection>(); | |
| while (pos < sectionPos + payloadLen) { | |
| auto oldPos = pos; | |
| auto dylinkType = getU32LEB(); | |
| if (lastType && dylinkType <= lastType) { | |
| std::cerr << "warning: out-of-order dylink.0 subsection: " << dylinkType | |
| << std::endl; | |
| } | |
| lastType = dylinkType; | |
| auto subsectionSize = getU32LEB(); | |
| auto subsectionPos = pos; | |
| if (dylinkType == BinaryConsts::CustomSections::Subsection::DylinkMemInfo) { | |
| wasm.dylinkSection->memorySize = getU32LEB(); | |
| wasm.dylinkSection->memoryAlignment = getU32LEB(); | |
| wasm.dylinkSection->tableSize = getU32LEB(); | |
| wasm.dylinkSection->tableAlignment = getU32LEB(); | |
| } else if (dylinkType == | |
| BinaryConsts::CustomSections::Subsection::DylinkNeeded) { | |
| size_t numNeededDynlibs = getU32LEB(); | |
| for (size_t i = 0; i < numNeededDynlibs; ++i) { | |
| wasm.dylinkSection->neededDynlibs.push_back(getInlineString()); | |
| } | |
| } else { | |
| // Unknown subsection. Stop parsing now and store the rest of | |
| // the section verbatim. | |
| pos = oldPos; | |
| size_t remaining = (sectionPos + payloadLen) - pos; | |
| auto tail = getByteView(remaining); | |
| wasm.dylinkSection->tail = {tail.begin(), tail.end()}; | |
| break; | |
| } | |
| if (pos != subsectionPos + subsectionSize) { | |
| throwError("bad dylink.0 subsection position change"); | |
| } | |
| } | |
| } | |
| template<typename ReadFunc> | |
| void WasmBinaryReader::readExpressionHints(Name sectionName, | |
| size_t payloadLen, | |
| ReadFunc read) { | |
| auto sectionPos = pos; | |
| auto numFuncs = getU32LEB(); | |
| for (Index i = 0; i < numFuncs; i++) { | |
| auto funcIndex = getU32LEB(); | |
| if (funcIndex >= wasm.functions.size()) { | |
| throwError("bad function in " + sectionName.toString()); | |
| } | |
| auto& func = wasm.functions[funcIndex]; | |
| // The encoded offsets we read below are relative to the start of the | |
| // function's locals (the declarations). | |
| auto funcLocalsOffset = func->funcLocation.declarations; | |
| // We have a map of expressions to their locations. Invert that to get the | |
| // map we will use below, from offsets to expressions. | |
| std::unordered_map<BinaryLocation, Expression*> locationsMap; | |
| for (auto& [expr, span] : func->expressionLocations) { | |
| locationsMap[span.start] = expr; | |
| } | |
| auto numHints = getU32LEB(); | |
| for (Index hint = 0; hint < numHints; hint++) { | |
| // Find the expression this hint is for. If the relative offset is 0, then | |
| // it is for the entire function, with expr==null. | |
| auto relativeOffset = getU32LEB(); | |
| if (relativeOffset == 0) { | |
| // Function-level annotations have the offset of the start of the | |
| // function. | |
| read(func->funcAnnotations); | |
| continue; | |
| } | |
| // To get the absolute offset, add the function's offset. | |
| auto absoluteOffset = funcLocalsOffset + relativeOffset; | |
| auto iter = locationsMap.find(absoluteOffset); | |
| if (iter == locationsMap.end()) { | |
| throwError("bad offset in " + sectionName.toString()); | |
| } | |
| auto* expr = iter->second; | |
| read(func->codeAnnotations[expr]); | |
| } | |
| } | |
| if (pos != sectionPos + payloadLen) { | |
| throwError("bad BranchHint section size"); | |
| } | |
| } | |
| void WasmBinaryReader::readBranchHints(size_t payloadLen) { | |
| readExpressionHints( | |
| Annotations::BranchHint, payloadLen, [&](CodeAnnotation& annotation) { | |
| auto size = getU32LEB(); | |
| if (size != 1) { | |
| throwError("bad BranchHint size"); | |
| } | |
| auto likely = getU32LEB(); | |
| if (likely != 0 && likely != 1) { | |
| throwError("bad BranchHint value"); | |
| } | |
| annotation.branchLikely = likely; | |
| }); | |
| } | |
| // Reads a simple i7 hint, in the range [0..127]. | |
| #define READ_I7_HINT(code, field) \ | |
| readExpressionHints(code, payloadLen, [&](CodeAnnotation& annotation) { \ | |
| auto size = getU32LEB(); \ | |
| if (size != 1) { \ | |
| throwError("bad InlineHint size"); \ | |
| } \ | |
| uint8_t field = getInt8(); \ | |
| if (field > 127) { \ | |
| throwError("bad InlineHint value"); \ | |
| } \ | |
| annotation.field = field; \ | |
| }); | |
| void WasmBinaryReader::readInlineHints(size_t payloadLen) { | |
| READ_I7_HINT(Annotations::InlineHint, inline_); | |
| } | |
| // Reads a simple boolean hint of size 0. Receives the code and the field name | |
| // on the annotation object. | |
| #define READ_BOOLEAN_HINT(code, field) \ | |
| readExpressionHints(code, payloadLen, [&](CodeAnnotation& annotation) { \ | |
| auto size = getU32LEB(); \ | |
| if (size != 0) { \ | |
| throwError("bad " #field " hint size"); \ | |
| } \ | |
| annotation.field = true; \ | |
| }); | |
| void WasmBinaryReader::readRemovableIfUnusedHints(size_t payloadLen) { | |
| READ_BOOLEAN_HINT(Annotations::RemovableIfUnusedHint, removableIfUnused); | |
| } | |
| void WasmBinaryReader::readJSCalledHints(size_t payloadLen) { | |
| READ_BOOLEAN_HINT(Annotations::JSCalledHint, jsCalled); | |
| } | |
| void WasmBinaryReader::readIdempotentHints(size_t payloadLen) { | |
| READ_BOOLEAN_HINT(Annotations::IdempotentHint, idempotent); | |
| } | |
| void WasmBinaryReader::readToolchainInlineHints(size_t payloadLen) { | |
| READ_I7_HINT(Annotations::ToolchainInlineHint, toolchainInline); | |
| } | |
| void WasmBinaryReader::readNoReturnHints(size_t payloadLen) { | |
| READ_BOOLEAN_HINT(Annotations::NoReturnHint, noReturn); | |
| } | |
| std::tuple<Address, Address, Index, MemoryOrder, std::optional<HeapType>> | |
| WasmBinaryReader::readMemoryAccess(bool isAtomic, bool isRMW) { | |
| auto rawAlignment = getU32LEB(); | |
| Index memIdx = 0; | |
| Address offset = 0; | |
| std::optional<HeapType> arrayType; | |
| bool hasMemoryOrder = rawAlignment & BinaryConsts::HasMemoryOrderMask; | |
| if (hasMemoryOrder && !isAtomic) { | |
| throwError("Memory order may only be set for atomic instructions."); | |
| } | |
| if (hasMemoryOrder) { | |
| // Clear the bit before we parse alignment | |
| rawAlignment = rawAlignment & ~BinaryConsts::HasMemoryOrderMask; | |
| } | |
| bool hasMemIdx = rawAlignment & BinaryConsts::HasMemoryIndexMask; | |
| if (hasMemIdx) { | |
| // Clear the bit before we parse alignment | |
| rawAlignment = rawAlignment & ~BinaryConsts::HasMemoryIndexMask; | |
| } | |
| bool hasBackingArray = rawAlignment & BinaryConsts::HasBackingArrayMask; | |
| if (hasBackingArray) { | |
| // Clear the bit before we parse alignment | |
| rawAlignment = rawAlignment & ~BinaryConsts::HasBackingArrayMask; | |
| } | |
| if (rawAlignment > 8) { | |
| throwError("Alignment must be of a reasonable size"); | |
| } | |
| Address alignment = Bits::pow2(rawAlignment); | |
| MemoryOrder memoryOrder = | |
| isAtomic ? MemoryOrder::SeqCst : MemoryOrder::Unordered; | |
| if (hasBackingArray) { | |
| if (hasMemIdx || hasMemoryOrder) { | |
| throwError( | |
| "Memory index and memory order are not allowed for array backing."); | |
| } | |
| arrayType = getIndexedHeapType(); | |
| offset = getU32LEB(); | |
| } else { | |
| if (hasMemIdx) { | |
| memIdx = getU32LEB(); | |
| } | |
| if (hasMemoryOrder) { | |
| memoryOrder = getMemoryOrder(isRMW); | |
| } | |
| if (memIdx >= wasm.memories.size()) { | |
| throwError("Memory index out of range while reading memory alignment."); | |
| } | |
| auto* memory = wasm.memories[memIdx].get(); | |
| offset = memory->addressType == Type::i32 ? getU32LEB() : getU64LEB(); | |
| } | |
| return {alignment, offset, memIdx, memoryOrder, arrayType}; | |
| } | |
| std::tuple<Name, Address, Address, MemoryOrder> | |
| WasmBinaryReader::getAtomicMemarg() { | |
| auto [alignment, offset, memIdx, memoryOrder, arrayType] = | |
| readMemoryAccess(/*isAtomic=*/true, /*isRMW=*/false); | |
| return {getMemoryName(memIdx), alignment, offset, memoryOrder}; | |
| } | |
| std::tuple<Name, Address, Address, MemoryOrder> | |
| WasmBinaryReader::getRMWMemarg() { | |
| auto [alignment, offset, memIdx, memoryOrder, arrayType] = | |
| readMemoryAccess(/*isAtomic=*/true, /*isRMW=*/true); | |
| return {getMemoryName(memIdx), alignment, offset, memoryOrder}; | |
| } | |
| std::tuple<Name, Address, Address, std::optional<HeapType>> | |
| WasmBinaryReader::getMemarg() { | |
| auto [alignment, offset, memIdx, memoryOrder, arrayType] = | |
| readMemoryAccess(/*isAtomic=*/false, /*isRMW=*/false); | |
| if (arrayType) { | |
| return {Name(), alignment, offset, arrayType}; | |
| } | |
| return {getMemoryName(memIdx), alignment, offset, std::nullopt}; | |
| } | |
| MemoryOrder WasmBinaryReader::getMemoryOrder(bool isRMW) { | |
| auto code = getInt8(); | |
| switch (code) { | |
| case BinaryConsts::OrderSeqCst: | |
| // Covers the RMW case as well because (0 << 4 ) | 0 == 0. | |
| return MemoryOrder::SeqCst; | |
| case BinaryConsts::OrderAcqRel: | |
| if (!isRMW) { | |
| return MemoryOrder::AcqRel; | |
| } | |
| throwError("RMW memory orders must match"); | |
| case ((BinaryConsts::OrderAcqRel << 4) | BinaryConsts::OrderAcqRel): | |
| if (isRMW) { | |
| return MemoryOrder::AcqRel; | |
| } | |
| break; | |
| case BinaryConsts::OrderRelaxed: | |
| if (!isRMW) { | |
| return MemoryOrder::Relaxed; | |
| } | |
| throwError("RMW memory orders must match"); | |
| case ((BinaryConsts::OrderRelaxed << 4) | BinaryConsts::OrderRelaxed): | |
| if (isRMW) { | |
| return MemoryOrder::Relaxed; | |
| } | |
| break; | |
| } | |
| throwError("Unrecognized memory order code " + std::to_string(code)); | |
| } | |
| } // namespace wasm |