blob: 61f59c76aa964cba4b93a8e7473932b46b6f8903 [file] [log] [blame] [edit]
/*
* Copyright 2019 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 "wasm-stack.h"
#include "ir/find_all.h"
#include "ir/properties.h"
#include "wasm-binary.h"
#include "wasm-debug.h"
namespace wasm {
static Name IMPOSSIBLE_CONTINUE("impossible-continue");
void BinaryInstWriter::emitResultType(Type type) {
if (type == Type::unreachable) {
parent.writeType(Type::none);
} else if (type.isTuple()) {
o << S32LEB(parent.getSignatureIndex(Signature(Type::none, type)));
} else {
parent.writeType(type);
}
}
void BinaryInstWriter::visitBlock(Block* curr) {
breakStack.push_back(curr->name);
o << int8_t(BinaryConsts::Block);
emitResultType(curr->type);
}
void BinaryInstWriter::visitIf(If* curr) {
// the binary format requires this; we have a block if we need one
// TODO: optimize this in Stack IR (if child is a block, we may break to this
// instead)
breakStack.emplace_back(IMPOSSIBLE_CONTINUE);
o << int8_t(BinaryConsts::If);
emitResultType(curr->type);
}
void BinaryInstWriter::emitIfElse(If* curr) {
if (func && !sourceMap) {
parent.writeExtraDebugLocation(curr, func, BinaryLocations::Else);
}
o << int8_t(BinaryConsts::Else);
}
void BinaryInstWriter::visitLoop(Loop* curr) {
breakStack.push_back(curr->name);
o << int8_t(BinaryConsts::Loop);
emitResultType(curr->type);
}
void BinaryInstWriter::visitBreak(Break* curr) {
o << int8_t(curr->condition ? BinaryConsts::BrIf : BinaryConsts::Br)
<< U32LEB(getBreakIndex(curr->name));
// See comment on |brIfsNeedingHandling| for the extra casts we need to emit
// here for certain br_ifs.
auto iter = brIfsNeedingHandling.find(curr);
if (iter != brIfsNeedingHandling.end()) {
auto unrefinedType = iter->second;
auto type = curr->type;
assert(type.size() == unrefinedType.size());
assert(curr->type.hasRef());
auto emitCast = [&](Type to) {
// Shim a tiny bit of IR, just enough to get visitRefCast to see what we
// are casting, and to emit the proper thing.
RefCast cast;
cast.type = to;
cast.ref = nullptr;
visitRefCast(&cast);
};
if (!type.isTuple()) {
// Simple: Just emit a cast, and then the type matches Binaryen IR's.
emitCast(type);
} else {
// Tuples are trickier to handle, and we need to use scratch locals. Stash
// all the values on the stack to those locals, then reload them, casting
// as we go.
//
// We must track how many scratch locals we've used from each type as we
// go, as a type might appear multiple times in the tuple. We allocated
// enough for each, in a contiguous range, so we just increment as we go.
std::unordered_map<Type, Index> scratchTypeUses;
for (Index i = 0; i < unrefinedType.size(); i++) {
auto t = unrefinedType[unrefinedType.size() - i - 1];
assert(scratchLocals.find(t) != scratchLocals.end());
auto localIndex = scratchLocals[t] + scratchTypeUses[t]++;
o << int8_t(BinaryConsts::LocalSet) << U32LEB(localIndex);
}
for (Index i = 0; i < unrefinedType.size(); i++) {
auto t = unrefinedType[i];
auto localIndex = scratchLocals[t] + --scratchTypeUses[t];
o << int8_t(BinaryConsts::LocalGet) << U32LEB(localIndex);
if (t.isRef()) {
// Note that we cast all types here, when perhaps only some of the
// tuple's lanes need that. This is simpler.
emitCast(type[i]);
}
}
}
}
}
void BinaryInstWriter::visitSwitch(Switch* curr) {
o << int8_t(BinaryConsts::BrTable) << U32LEB(curr->targets.size());
for (auto target : curr->targets) {
o << U32LEB(getBreakIndex(target));
}
o << U32LEB(getBreakIndex(curr->default_));
}
void BinaryInstWriter::visitCall(Call* curr) {
int8_t op =
curr->isReturn ? BinaryConsts::RetCallFunction : BinaryConsts::CallFunction;
o << op << U32LEB(parent.getFunctionIndex(curr->target));
}
void BinaryInstWriter::visitCallIndirect(CallIndirect* curr) {
Index tableIdx = parent.getTableIndex(curr->table);
int8_t op =
curr->isReturn ? BinaryConsts::RetCallIndirect : BinaryConsts::CallIndirect;
o << op << U32LEB(parent.getTypeIndex(curr->heapType)) << U32LEB(tableIdx);
}
void BinaryInstWriter::visitLocalGet(LocalGet* curr) {
if (deferredGets.count(curr)) {
// This local.get will be emitted as part of the instruction that consumes
// it.
return;
}
if (auto it = extractedGets.find(curr); it != extractedGets.end()) {
// We have a tuple of locals to get, but we will only end up using one of
// them, so we can just emit that one.
o << int8_t(BinaryConsts::LocalGet)
<< U32LEB(mappedLocals[std::make_pair(curr->index, it->second)]);
return;
}
size_t numValues = func->getLocalType(curr->index).size();
for (Index i = 0; i < numValues; ++i) {
o << int8_t(BinaryConsts::LocalGet)
<< U32LEB(mappedLocals[std::make_pair(curr->index, i)]);
}
}
void BinaryInstWriter::visitLocalSet(LocalSet* curr) {
size_t numValues = func->getLocalType(curr->index).size();
// If this is a tuple, set all the elements with nonzero index.
for (Index i = numValues - 1; i >= 1; --i) {
o << int8_t(BinaryConsts::LocalSet)
<< U32LEB(mappedLocals[std::make_pair(curr->index, i)]);
}
if (!curr->isTee()) {
// This is not a tee, so just finish setting the values.
o << int8_t(BinaryConsts::LocalSet)
<< U32LEB(mappedLocals[std::make_pair(curr->index, 0)]);
} else if (auto it = extractedGets.find(curr); it != extractedGets.end()) {
// We only need to get the single extracted value.
if (it->second == 0) {
o << int8_t(BinaryConsts::LocalTee)
<< U32LEB(mappedLocals[std::make_pair(curr->index, 0)]);
} else {
o << int8_t(BinaryConsts::LocalSet)
<< U32LEB(mappedLocals[std::make_pair(curr->index, 0)]);
o << int8_t(BinaryConsts::LocalGet)
<< U32LEB(mappedLocals[std::make_pair(curr->index, it->second)]);
}
} else {
// We need to get all the values.
o << int8_t(BinaryConsts::LocalTee)
<< U32LEB(mappedLocals[std::make_pair(curr->index, 0)]);
for (Index i = 1; i < numValues; ++i) {
o << int8_t(BinaryConsts::LocalGet)
<< U32LEB(mappedLocals[std::make_pair(curr->index, i)]);
}
}
}
void BinaryInstWriter::visitGlobalGet(GlobalGet* curr) {
Index index = parent.getGlobalIndex(curr->name);
if (auto it = extractedGets.find(curr); it != extractedGets.end()) {
// We have a tuple of globals to get, but we will only end up using one of
// them, so we can just emit that one.
o << int8_t(BinaryConsts::GlobalGet) << U32LEB(index + it->second);
return;
}
// Emit a global.get for each element if this is a tuple global
size_t numValues = curr->type.size();
for (Index i = 0; i < numValues; ++i) {
o << int8_t(BinaryConsts::GlobalGet) << U32LEB(index + i);
}
}
void BinaryInstWriter::visitGlobalSet(GlobalSet* curr) {
// Emit a global.set for each element if this is a tuple global
Index index = parent.getGlobalIndex(curr->name);
size_t numValues = parent.getModule()->getGlobal(curr->name)->type.size();
for (int i = numValues - 1; i >= 0; --i) {
o << int8_t(BinaryConsts::GlobalSet) << U32LEB(index + i);
}
}
void BinaryInstWriter::visitLoad(Load* curr) {
if (!curr->isAtomic) {
switch (curr->type.getBasic()) {
case Type::i32: {
switch (curr->bytes) {
case 1:
o << int8_t(curr->signed_ ? BinaryConsts::I32LoadMem8S
: BinaryConsts::I32LoadMem8U);
break;
case 2:
o << int8_t(curr->signed_ ? BinaryConsts::I32LoadMem16S
: BinaryConsts::I32LoadMem16U);
break;
case 4:
o << int8_t(BinaryConsts::I32LoadMem);
break;
default:
abort();
}
break;
}
case Type::i64: {
switch (curr->bytes) {
case 1:
o << int8_t(curr->signed_ ? BinaryConsts::I64LoadMem8S
: BinaryConsts::I64LoadMem8U);
break;
case 2:
o << int8_t(curr->signed_ ? BinaryConsts::I64LoadMem16S
: BinaryConsts::I64LoadMem16U);
break;
case 4:
o << int8_t(curr->signed_ ? BinaryConsts::I64LoadMem32S
: BinaryConsts::I64LoadMem32U);
break;
case 8:
o << int8_t(BinaryConsts::I64LoadMem);
break;
default:
abort();
}
break;
}
case Type::f32: {
switch (curr->bytes) {
case 2:
o << int8_t(BinaryConsts::MiscPrefix)
<< U32LEB(BinaryConsts::F32_F16LoadMem);
break;
case 4:
o << int8_t(BinaryConsts::F32LoadMem);
break;
default:
WASM_UNREACHABLE("invalid load size");
}
break;
}
case Type::f64:
o << int8_t(BinaryConsts::F64LoadMem);
break;
case Type::v128:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::V128Load);
break;
case Type::unreachable:
// the pointer is unreachable, so we are never reached; just don't emit
// a load
return;
case Type::none:
WASM_UNREACHABLE("unexpected type");
}
} else {
o << int8_t(BinaryConsts::AtomicPrefix);
switch (curr->type.getBasic()) {
case Type::i32: {
switch (curr->bytes) {
case 1:
o << int8_t(BinaryConsts::I32AtomicLoad8U);
break;
case 2:
o << int8_t(BinaryConsts::I32AtomicLoad16U);
break;
case 4:
o << int8_t(BinaryConsts::I32AtomicLoad);
break;
default:
WASM_UNREACHABLE("invalid load size");
}
break;
}
case Type::i64: {
switch (curr->bytes) {
case 1:
o << int8_t(BinaryConsts::I64AtomicLoad8U);
break;
case 2:
o << int8_t(BinaryConsts::I64AtomicLoad16U);
break;
case 4:
o << int8_t(BinaryConsts::I64AtomicLoad32U);
break;
case 8:
o << int8_t(BinaryConsts::I64AtomicLoad);
break;
default:
WASM_UNREACHABLE("invalid load size");
}
break;
}
case Type::unreachable:
return;
default:
WASM_UNREACHABLE("unexpected type");
}
}
emitMemoryAccess(curr->align, curr->bytes, curr->offset, curr->memory);
}
void BinaryInstWriter::visitStore(Store* curr) {
if (!curr->isAtomic) {
switch (curr->valueType.getBasic()) {
case Type::i32: {
switch (curr->bytes) {
case 1:
o << int8_t(BinaryConsts::I32StoreMem8);
break;
case 2:
o << int8_t(BinaryConsts::I32StoreMem16);
break;
case 4:
o << int8_t(BinaryConsts::I32StoreMem);
break;
default:
abort();
}
break;
}
case Type::i64: {
switch (curr->bytes) {
case 1:
o << int8_t(BinaryConsts::I64StoreMem8);
break;
case 2:
o << int8_t(BinaryConsts::I64StoreMem16);
break;
case 4:
o << int8_t(BinaryConsts::I64StoreMem32);
break;
case 8:
o << int8_t(BinaryConsts::I64StoreMem);
break;
default:
abort();
}
break;
}
case Type::f32: {
switch (curr->bytes) {
case 2:
o << int8_t(BinaryConsts::MiscPrefix)
<< U32LEB(BinaryConsts::F32_F16StoreMem);
break;
case 4:
o << int8_t(BinaryConsts::F32StoreMem);
break;
default:
WASM_UNREACHABLE("invalid store size");
}
break;
}
case Type::f64:
o << int8_t(BinaryConsts::F64StoreMem);
break;
case Type::v128:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::V128Store);
break;
case Type::none:
case Type::unreachable:
WASM_UNREACHABLE("unexpected type");
}
} else {
o << int8_t(BinaryConsts::AtomicPrefix);
switch (curr->valueType.getBasic()) {
case Type::i32: {
switch (curr->bytes) {
case 1:
o << int8_t(BinaryConsts::I32AtomicStore8);
break;
case 2:
o << int8_t(BinaryConsts::I32AtomicStore16);
break;
case 4:
o << int8_t(BinaryConsts::I32AtomicStore);
break;
default:
WASM_UNREACHABLE("invalid store size");
}
break;
}
case Type::i64: {
switch (curr->bytes) {
case 1:
o << int8_t(BinaryConsts::I64AtomicStore8);
break;
case 2:
o << int8_t(BinaryConsts::I64AtomicStore16);
break;
case 4:
o << int8_t(BinaryConsts::I64AtomicStore32);
break;
case 8:
o << int8_t(BinaryConsts::I64AtomicStore);
break;
default:
WASM_UNREACHABLE("invalid store size");
}
break;
}
default:
WASM_UNREACHABLE("unexpected type");
}
}
emitMemoryAccess(curr->align, curr->bytes, curr->offset, curr->memory);
}
void BinaryInstWriter::visitAtomicRMW(AtomicRMW* curr) {
o << int8_t(BinaryConsts::AtomicPrefix);
#define CASE_FOR_OP(Op) \
case RMW##Op: \
switch (curr->type.getBasic()) { \
case Type::i32: \
switch (curr->bytes) { \
case 1: \
o << int8_t(BinaryConsts::I32AtomicRMW##Op##8U); \
break; \
case 2: \
o << int8_t(BinaryConsts::I32AtomicRMW##Op##16U); \
break; \
case 4: \
o << int8_t(BinaryConsts::I32AtomicRMW##Op); \
break; \
default: \
WASM_UNREACHABLE("invalid rmw size"); \
} \
break; \
case Type::i64: \
switch (curr->bytes) { \
case 1: \
o << int8_t(BinaryConsts::I64AtomicRMW##Op##8U); \
break; \
case 2: \
o << int8_t(BinaryConsts::I64AtomicRMW##Op##16U); \
break; \
case 4: \
o << int8_t(BinaryConsts::I64AtomicRMW##Op##32U); \
break; \
case 8: \
o << int8_t(BinaryConsts::I64AtomicRMW##Op); \
break; \
default: \
WASM_UNREACHABLE("invalid rmw size"); \
} \
break; \
default: \
WASM_UNREACHABLE("unexpected type"); \
} \
break
switch (curr->op) {
CASE_FOR_OP(Add);
CASE_FOR_OP(Sub);
CASE_FOR_OP(And);
CASE_FOR_OP(Or);
CASE_FOR_OP(Xor);
CASE_FOR_OP(Xchg);
default:
WASM_UNREACHABLE("unexpected op");
}
#undef CASE_FOR_OP
emitMemoryAccess(curr->bytes, curr->bytes, curr->offset, curr->memory);
}
void BinaryInstWriter::visitAtomicCmpxchg(AtomicCmpxchg* curr) {
o << int8_t(BinaryConsts::AtomicPrefix);
switch (curr->type.getBasic()) {
case Type::i32:
switch (curr->bytes) {
case 1:
o << int8_t(BinaryConsts::I32AtomicCmpxchg8U);
break;
case 2:
o << int8_t(BinaryConsts::I32AtomicCmpxchg16U);
break;
case 4:
o << int8_t(BinaryConsts::I32AtomicCmpxchg);
break;
default:
WASM_UNREACHABLE("invalid size");
}
break;
case Type::i64:
switch (curr->bytes) {
case 1:
o << int8_t(BinaryConsts::I64AtomicCmpxchg8U);
break;
case 2:
o << int8_t(BinaryConsts::I64AtomicCmpxchg16U);
break;
case 4:
o << int8_t(BinaryConsts::I64AtomicCmpxchg32U);
break;
case 8:
o << int8_t(BinaryConsts::I64AtomicCmpxchg);
break;
default:
WASM_UNREACHABLE("invalid size");
}
break;
default:
WASM_UNREACHABLE("unexpected type");
}
emitMemoryAccess(curr->bytes, curr->bytes, curr->offset, curr->memory);
}
void BinaryInstWriter::visitAtomicWait(AtomicWait* curr) {
o << int8_t(BinaryConsts::AtomicPrefix);
switch (curr->expectedType.getBasic()) {
case Type::i32: {
o << int8_t(BinaryConsts::I32AtomicWait);
emitMemoryAccess(4, 4, curr->offset, curr->memory);
break;
}
case Type::i64: {
o << int8_t(BinaryConsts::I64AtomicWait);
emitMemoryAccess(8, 8, curr->offset, curr->memory);
break;
}
default:
WASM_UNREACHABLE("unexpected type");
}
}
void BinaryInstWriter::visitAtomicNotify(AtomicNotify* curr) {
o << int8_t(BinaryConsts::AtomicPrefix) << int8_t(BinaryConsts::AtomicNotify);
emitMemoryAccess(4, 4, curr->offset, curr->memory);
}
void BinaryInstWriter::visitAtomicFence(AtomicFence* curr) {
o << int8_t(BinaryConsts::AtomicPrefix) << int8_t(BinaryConsts::AtomicFence)
<< int8_t(curr->order);
}
void BinaryInstWriter::visitSIMDExtract(SIMDExtract* curr) {
o << int8_t(BinaryConsts::SIMDPrefix);
switch (curr->op) {
case ExtractLaneSVecI8x16:
o << U32LEB(BinaryConsts::I8x16ExtractLaneS);
break;
case ExtractLaneUVecI8x16:
o << U32LEB(BinaryConsts::I8x16ExtractLaneU);
break;
case ExtractLaneSVecI16x8:
o << U32LEB(BinaryConsts::I16x8ExtractLaneS);
break;
case ExtractLaneUVecI16x8:
o << U32LEB(BinaryConsts::I16x8ExtractLaneU);
break;
case ExtractLaneVecI32x4:
o << U32LEB(BinaryConsts::I32x4ExtractLane);
break;
case ExtractLaneVecI64x2:
o << U32LEB(BinaryConsts::I64x2ExtractLane);
break;
case ExtractLaneVecF16x8:
o << U32LEB(BinaryConsts::F16x8ExtractLane);
break;
case ExtractLaneVecF32x4:
o << U32LEB(BinaryConsts::F32x4ExtractLane);
break;
case ExtractLaneVecF64x2:
o << U32LEB(BinaryConsts::F64x2ExtractLane);
break;
}
o << uint8_t(curr->index);
}
void BinaryInstWriter::visitSIMDReplace(SIMDReplace* curr) {
o << int8_t(BinaryConsts::SIMDPrefix);
switch (curr->op) {
case ReplaceLaneVecI8x16:
o << U32LEB(BinaryConsts::I8x16ReplaceLane);
break;
case ReplaceLaneVecI16x8:
o << U32LEB(BinaryConsts::I16x8ReplaceLane);
break;
case ReplaceLaneVecI32x4:
o << U32LEB(BinaryConsts::I32x4ReplaceLane);
break;
case ReplaceLaneVecI64x2:
o << U32LEB(BinaryConsts::I64x2ReplaceLane);
break;
case ReplaceLaneVecF16x8:
o << U32LEB(BinaryConsts::F16x8ReplaceLane);
break;
case ReplaceLaneVecF32x4:
o << U32LEB(BinaryConsts::F32x4ReplaceLane);
break;
case ReplaceLaneVecF64x2:
o << U32LEB(BinaryConsts::F64x2ReplaceLane);
break;
}
assert(curr->index < 16);
o << uint8_t(curr->index);
}
void BinaryInstWriter::visitSIMDShuffle(SIMDShuffle* curr) {
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16Shuffle);
for (uint8_t m : curr->mask) {
o << m;
}
}
void BinaryInstWriter::visitSIMDTernary(SIMDTernary* curr) {
o << int8_t(BinaryConsts::SIMDPrefix);
switch (curr->op) {
case Bitselect:
o << U32LEB(BinaryConsts::V128Bitselect);
break;
case LaneselectI8x16:
o << U32LEB(BinaryConsts::I8x16Laneselect);
break;
case LaneselectI16x8:
o << U32LEB(BinaryConsts::I16x8Laneselect);
break;
case LaneselectI32x4:
o << U32LEB(BinaryConsts::I32x4Laneselect);
break;
case LaneselectI64x2:
o << U32LEB(BinaryConsts::I64x2Laneselect);
break;
case RelaxedMaddVecF16x8:
o << U32LEB(BinaryConsts::F16x8RelaxedMadd);
break;
case RelaxedNmaddVecF16x8:
o << U32LEB(BinaryConsts::F16x8RelaxedNmadd);
break;
case RelaxedMaddVecF32x4:
o << U32LEB(BinaryConsts::F32x4RelaxedMadd);
break;
case RelaxedNmaddVecF32x4:
o << U32LEB(BinaryConsts::F32x4RelaxedNmadd);
break;
case RelaxedMaddVecF64x2:
o << U32LEB(BinaryConsts::F64x2RelaxedMadd);
break;
case RelaxedNmaddVecF64x2:
o << U32LEB(BinaryConsts::F64x2RelaxedNmadd);
break;
case DotI8x16I7x16AddSToVecI32x4:
o << U32LEB(BinaryConsts::I32x4DotI8x16I7x16AddS);
break;
}
}
void BinaryInstWriter::visitSIMDShift(SIMDShift* curr) {
o << int8_t(BinaryConsts::SIMDPrefix);
switch (curr->op) {
case ShlVecI8x16:
o << U32LEB(BinaryConsts::I8x16Shl);
break;
case ShrSVecI8x16:
o << U32LEB(BinaryConsts::I8x16ShrS);
break;
case ShrUVecI8x16:
o << U32LEB(BinaryConsts::I8x16ShrU);
break;
case ShlVecI16x8:
o << U32LEB(BinaryConsts::I16x8Shl);
break;
case ShrSVecI16x8:
o << U32LEB(BinaryConsts::I16x8ShrS);
break;
case ShrUVecI16x8:
o << U32LEB(BinaryConsts::I16x8ShrU);
break;
case ShlVecI32x4:
o << U32LEB(BinaryConsts::I32x4Shl);
break;
case ShrSVecI32x4:
o << U32LEB(BinaryConsts::I32x4ShrS);
break;
case ShrUVecI32x4:
o << U32LEB(BinaryConsts::I32x4ShrU);
break;
case ShlVecI64x2:
o << U32LEB(BinaryConsts::I64x2Shl);
break;
case ShrSVecI64x2:
o << U32LEB(BinaryConsts::I64x2ShrS);
break;
case ShrUVecI64x2:
o << U32LEB(BinaryConsts::I64x2ShrU);
break;
}
}
void BinaryInstWriter::visitSIMDLoad(SIMDLoad* curr) {
o << int8_t(BinaryConsts::SIMDPrefix);
switch (curr->op) {
case Load8SplatVec128:
o << U32LEB(BinaryConsts::V128Load8Splat);
break;
case Load16SplatVec128:
o << U32LEB(BinaryConsts::V128Load16Splat);
break;
case Load32SplatVec128:
o << U32LEB(BinaryConsts::V128Load32Splat);
break;
case Load64SplatVec128:
o << U32LEB(BinaryConsts::V128Load64Splat);
break;
case Load8x8SVec128:
o << U32LEB(BinaryConsts::V128Load8x8S);
break;
case Load8x8UVec128:
o << U32LEB(BinaryConsts::V128Load8x8U);
break;
case Load16x4SVec128:
o << U32LEB(BinaryConsts::V128Load16x4S);
break;
case Load16x4UVec128:
o << U32LEB(BinaryConsts::V128Load16x4U);
break;
case Load32x2SVec128:
o << U32LEB(BinaryConsts::V128Load32x2S);
break;
case Load32x2UVec128:
o << U32LEB(BinaryConsts::V128Load32x2U);
break;
case Load32ZeroVec128:
o << U32LEB(BinaryConsts::V128Load32Zero);
break;
case Load64ZeroVec128:
o << U32LEB(BinaryConsts::V128Load64Zero);
break;
}
assert(curr->align);
emitMemoryAccess(
curr->align, /*(unused) bytes=*/0, curr->offset, curr->memory);
}
void BinaryInstWriter::visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) {
o << int8_t(BinaryConsts::SIMDPrefix);
switch (curr->op) {
case Load8LaneVec128:
o << U32LEB(BinaryConsts::V128Load8Lane);
break;
case Load16LaneVec128:
o << U32LEB(BinaryConsts::V128Load16Lane);
break;
case Load32LaneVec128:
o << U32LEB(BinaryConsts::V128Load32Lane);
break;
case Load64LaneVec128:
o << U32LEB(BinaryConsts::V128Load64Lane);
break;
case Store8LaneVec128:
o << U32LEB(BinaryConsts::V128Store8Lane);
break;
case Store16LaneVec128:
o << U32LEB(BinaryConsts::V128Store16Lane);
break;
case Store32LaneVec128:
o << U32LEB(BinaryConsts::V128Store32Lane);
break;
case Store64LaneVec128:
o << U32LEB(BinaryConsts::V128Store64Lane);
break;
}
assert(curr->align);
emitMemoryAccess(
curr->align, /*(unused) bytes=*/0, curr->offset, curr->memory);
o << curr->index;
}
void BinaryInstWriter::visitMemoryInit(MemoryInit* curr) {
o << int8_t(BinaryConsts::MiscPrefix);
o << U32LEB(BinaryConsts::MemoryInit);
o << U32LEB(parent.getDataSegmentIndex(curr->segment));
o << U32LEB(parent.getMemoryIndex(curr->memory));
}
void BinaryInstWriter::visitDataDrop(DataDrop* curr) {
o << int8_t(BinaryConsts::MiscPrefix);
o << U32LEB(BinaryConsts::DataDrop);
o << U32LEB(parent.getDataSegmentIndex(curr->segment));
}
void BinaryInstWriter::visitMemoryCopy(MemoryCopy* curr) {
o << int8_t(BinaryConsts::MiscPrefix);
o << U32LEB(BinaryConsts::MemoryCopy);
o << U32LEB(parent.getMemoryIndex(curr->destMemory));
o << U32LEB(parent.getMemoryIndex(curr->sourceMemory));
}
void BinaryInstWriter::visitMemoryFill(MemoryFill* curr) {
o << int8_t(BinaryConsts::MiscPrefix);
o << U32LEB(BinaryConsts::MemoryFill);
o << U32LEB(parent.getMemoryIndex(curr->memory));
}
void BinaryInstWriter::visitConst(Const* curr) {
switch (curr->type.getBasic()) {
case Type::i32: {
o << int8_t(BinaryConsts::I32Const) << S32LEB(curr->value.geti32());
break;
}
case Type::i64: {
o << int8_t(BinaryConsts::I64Const) << S64LEB(curr->value.geti64());
break;
}
case Type::f32: {
o << int8_t(BinaryConsts::F32Const) << curr->value.reinterpreti32();
break;
}
case Type::f64: {
o << int8_t(BinaryConsts::F64Const) << curr->value.reinterpreti64();
break;
}
case Type::v128: {
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::V128Const);
std::array<uint8_t, 16> v = curr->value.getv128();
for (size_t i = 0; i < 16; ++i) {
o << uint8_t(v[i]);
}
break;
}
case Type::none:
case Type::unreachable:
WASM_UNREACHABLE("unexpected type");
}
}
void BinaryInstWriter::visitUnary(Unary* curr) {
switch (curr->op) {
case ClzInt32:
o << int8_t(BinaryConsts::I32Clz);
break;
case CtzInt32:
o << int8_t(BinaryConsts::I32Ctz);
break;
case PopcntInt32:
o << int8_t(BinaryConsts::I32Popcnt);
break;
case EqZInt32:
o << int8_t(BinaryConsts::I32EqZ);
break;
case ClzInt64:
o << int8_t(BinaryConsts::I64Clz);
break;
case CtzInt64:
o << int8_t(BinaryConsts::I64Ctz);
break;
case PopcntInt64:
o << int8_t(BinaryConsts::I64Popcnt);
break;
case EqZInt64:
o << int8_t(BinaryConsts::I64EqZ);
break;
case NegFloat32:
o << int8_t(BinaryConsts::F32Neg);
break;
case AbsFloat32:
o << int8_t(BinaryConsts::F32Abs);
break;
case CeilFloat32:
o << int8_t(BinaryConsts::F32Ceil);
break;
case FloorFloat32:
o << int8_t(BinaryConsts::F32Floor);
break;
case TruncFloat32:
o << int8_t(BinaryConsts::F32Trunc);
break;
case NearestFloat32:
o << int8_t(BinaryConsts::F32Nearest);
break;
case SqrtFloat32:
o << int8_t(BinaryConsts::F32Sqrt);
break;
case NegFloat64:
o << int8_t(BinaryConsts::F64Neg);
break;
case AbsFloat64:
o << int8_t(BinaryConsts::F64Abs);
break;
case CeilFloat64:
o << int8_t(BinaryConsts::F64Ceil);
break;
case FloorFloat64:
o << int8_t(BinaryConsts::F64Floor);
break;
case TruncFloat64:
o << int8_t(BinaryConsts::F64Trunc);
break;
case NearestFloat64:
o << int8_t(BinaryConsts::F64Nearest);
break;
case SqrtFloat64:
o << int8_t(BinaryConsts::F64Sqrt);
break;
case ExtendSInt32:
o << int8_t(BinaryConsts::I64SExtendI32);
break;
case ExtendUInt32:
o << int8_t(BinaryConsts::I64UExtendI32);
break;
case WrapInt64:
o << int8_t(BinaryConsts::I32WrapI64);
break;
case TruncUFloat32ToInt32:
o << int8_t(BinaryConsts::I32UTruncF32);
break;
case TruncUFloat32ToInt64:
o << int8_t(BinaryConsts::I64UTruncF32);
break;
case TruncSFloat32ToInt32:
o << int8_t(BinaryConsts::I32STruncF32);
break;
case TruncSFloat32ToInt64:
o << int8_t(BinaryConsts::I64STruncF32);
break;
case TruncUFloat64ToInt32:
o << int8_t(BinaryConsts::I32UTruncF64);
break;
case TruncUFloat64ToInt64:
o << int8_t(BinaryConsts::I64UTruncF64);
break;
case TruncSFloat64ToInt32:
o << int8_t(BinaryConsts::I32STruncF64);
break;
case TruncSFloat64ToInt64:
o << int8_t(BinaryConsts::I64STruncF64);
break;
case ConvertUInt32ToFloat32:
o << int8_t(BinaryConsts::F32UConvertI32);
break;
case ConvertUInt32ToFloat64:
o << int8_t(BinaryConsts::F64UConvertI32);
break;
case ConvertSInt32ToFloat32:
o << int8_t(BinaryConsts::F32SConvertI32);
break;
case ConvertSInt32ToFloat64:
o << int8_t(BinaryConsts::F64SConvertI32);
break;
case ConvertUInt64ToFloat32:
o << int8_t(BinaryConsts::F32UConvertI64);
break;
case ConvertUInt64ToFloat64:
o << int8_t(BinaryConsts::F64UConvertI64);
break;
case ConvertSInt64ToFloat32:
o << int8_t(BinaryConsts::F32SConvertI64);
break;
case ConvertSInt64ToFloat64:
o << int8_t(BinaryConsts::F64SConvertI64);
break;
case DemoteFloat64:
o << int8_t(BinaryConsts::F32DemoteI64);
break;
case PromoteFloat32:
o << int8_t(BinaryConsts::F64PromoteF32);
break;
case ReinterpretFloat32:
o << int8_t(BinaryConsts::I32ReinterpretF32);
break;
case ReinterpretFloat64:
o << int8_t(BinaryConsts::I64ReinterpretF64);
break;
case ReinterpretInt32:
o << int8_t(BinaryConsts::F32ReinterpretI32);
break;
case ReinterpretInt64:
o << int8_t(BinaryConsts::F64ReinterpretI64);
break;
case ExtendS8Int32:
o << int8_t(BinaryConsts::I32ExtendS8);
break;
case ExtendS16Int32:
o << int8_t(BinaryConsts::I32ExtendS16);
break;
case ExtendS8Int64:
o << int8_t(BinaryConsts::I64ExtendS8);
break;
case ExtendS16Int64:
o << int8_t(BinaryConsts::I64ExtendS16);
break;
case ExtendS32Int64:
o << int8_t(BinaryConsts::I64ExtendS32);
break;
case TruncSatSFloat32ToInt32:
o << int8_t(BinaryConsts::MiscPrefix)
<< U32LEB(BinaryConsts::I32STruncSatF32);
break;
case TruncSatUFloat32ToInt32:
o << int8_t(BinaryConsts::MiscPrefix)
<< U32LEB(BinaryConsts::I32UTruncSatF32);
break;
case TruncSatSFloat64ToInt32:
o << int8_t(BinaryConsts::MiscPrefix)
<< U32LEB(BinaryConsts::I32STruncSatF64);
break;
case TruncSatUFloat64ToInt32:
o << int8_t(BinaryConsts::MiscPrefix)
<< U32LEB(BinaryConsts::I32UTruncSatF64);
break;
case TruncSatSFloat32ToInt64:
o << int8_t(BinaryConsts::MiscPrefix)
<< U32LEB(BinaryConsts::I64STruncSatF32);
break;
case TruncSatUFloat32ToInt64:
o << int8_t(BinaryConsts::MiscPrefix)
<< U32LEB(BinaryConsts::I64UTruncSatF32);
break;
case TruncSatSFloat64ToInt64:
o << int8_t(BinaryConsts::MiscPrefix)
<< U32LEB(BinaryConsts::I64STruncSatF64);
break;
case TruncSatUFloat64ToInt64:
o << int8_t(BinaryConsts::MiscPrefix)
<< U32LEB(BinaryConsts::I64UTruncSatF64);
break;
case SplatVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16Splat);
break;
case SplatVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8Splat);
break;
case SplatVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4Splat);
break;
case SplatVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I64x2Splat);
break;
case SplatVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Splat);
break;
case SplatVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Splat);
break;
case SplatVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Splat);
break;
case NotVec128:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::V128Not);
break;
case AnyTrueVec128:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::V128AnyTrue);
break;
case AbsVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16Abs);
break;
case NegVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16Neg);
break;
case AllTrueVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I8x16AllTrue);
break;
case BitmaskVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I8x16Bitmask);
break;
case PopcntVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I8x16Popcnt);
break;
case AbsVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8Abs);
break;
case NegVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8Neg);
break;
case AllTrueVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8AllTrue);
break;
case BitmaskVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8Bitmask);
break;
case AbsVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4Abs);
break;
case NegVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4Neg);
break;
case AllTrueVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4AllTrue);
break;
case BitmaskVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4Bitmask);
break;
case AbsVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I64x2Abs);
break;
case NegVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I64x2Neg);
break;
case AllTrueVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I64x2AllTrue);
break;
case BitmaskVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I64x2Bitmask);
break;
case AbsVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Abs);
break;
case NegVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Neg);
break;
case SqrtVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Sqrt);
break;
case CeilVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Ceil);
break;
case FloorVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Floor);
break;
case TruncVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Trunc);
break;
case NearestVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::F16x8Nearest);
break;
case AbsVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Abs);
break;
case NegVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Neg);
break;
case SqrtVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Sqrt);
break;
case CeilVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Ceil);
break;
case FloorVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Floor);
break;
case TruncVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Trunc);
break;
case NearestVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::F32x4Nearest);
break;
case AbsVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Abs);
break;
case NegVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Neg);
break;
case SqrtVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Sqrt);
break;
case CeilVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Ceil);
break;
case FloorVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Floor);
break;
case TruncVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Trunc);
break;
case NearestVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::F64x2Nearest);
break;
case ExtAddPairwiseSVecI8x16ToI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8ExtaddPairwiseI8x16S);
break;
case ExtAddPairwiseUVecI8x16ToI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8ExtaddPairwiseI8x16U);
break;
case ExtAddPairwiseSVecI16x8ToI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4ExtaddPairwiseI16x8S);
break;
case ExtAddPairwiseUVecI16x8ToI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4ExtaddPairwiseI16x8U);
break;
case TruncSatSVecF32x4ToVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4TruncSatF32x4S);
break;
case TruncSatUVecF32x4ToVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4TruncSatF32x4U);
break;
case ConvertSVecI32x4ToVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::F32x4ConvertI32x4S);
break;
case ConvertUVecI32x4ToVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::F32x4ConvertI32x4U);
break;
case ExtendLowSVecI8x16ToVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8ExtendLowI8x16S);
break;
case ExtendHighSVecI8x16ToVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8ExtendHighI8x16S);
break;
case ExtendLowUVecI8x16ToVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8ExtendLowI8x16U);
break;
case ExtendHighUVecI8x16ToVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8ExtendHighI8x16U);
break;
case ExtendLowSVecI16x8ToVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4ExtendLowI16x8S);
break;
case ExtendHighSVecI16x8ToVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4ExtendHighI16x8S);
break;
case ExtendLowUVecI16x8ToVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4ExtendLowI16x8U);
break;
case ExtendHighUVecI16x8ToVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4ExtendHighI16x8U);
break;
case ExtendLowSVecI32x4ToVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I64x2ExtendLowI32x4S);
break;
case ExtendHighSVecI32x4ToVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I64x2ExtendHighI32x4S);
break;
case ExtendLowUVecI32x4ToVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I64x2ExtendLowI32x4U);
break;
case ExtendHighUVecI32x4ToVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I64x2ExtendHighI32x4U);
break;
case ConvertLowSVecI32x4ToVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::F64x2ConvertLowI32x4S);
break;
case ConvertLowUVecI32x4ToVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::F64x2ConvertLowI32x4U);
break;
case TruncSatZeroSVecF64x2ToVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4TruncSatF64x2SZero);
break;
case TruncSatZeroUVecF64x2ToVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4TruncSatF64x2UZero);
break;
case DemoteZeroVecF64x2ToVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::F32x4DemoteF64x2Zero);
break;
case PromoteLowVecF32x4ToVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::F64x2PromoteLowF32x4);
break;
case RelaxedTruncSVecF32x4ToVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4RelaxedTruncF32x4S);
break;
case RelaxedTruncUVecF32x4ToVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4RelaxedTruncF32x4U);
break;
case RelaxedTruncZeroSVecF64x2ToVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4RelaxedTruncF64x2SZero);
break;
case RelaxedTruncZeroUVecF64x2ToVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4RelaxedTruncF64x2UZero);
break;
case TruncSatSVecF16x8ToVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8TruncSatF16x8S);
break;
case TruncSatUVecF16x8ToVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8TruncSatF16x8U);
break;
case ConvertSVecI16x8ToVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::F16x8ConvertI16x8S);
break;
case ConvertUVecI16x8ToVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::F16x8ConvertI16x8U);
break;
case InvalidUnary:
WASM_UNREACHABLE("invalid unary op");
}
}
void BinaryInstWriter::visitBinary(Binary* curr) {
switch (curr->op) {
case AddInt32:
o << int8_t(BinaryConsts::I32Add);
break;
case SubInt32:
o << int8_t(BinaryConsts::I32Sub);
break;
case MulInt32:
o << int8_t(BinaryConsts::I32Mul);
break;
case DivSInt32:
o << int8_t(BinaryConsts::I32DivS);
break;
case DivUInt32:
o << int8_t(BinaryConsts::I32DivU);
break;
case RemSInt32:
o << int8_t(BinaryConsts::I32RemS);
break;
case RemUInt32:
o << int8_t(BinaryConsts::I32RemU);
break;
case AndInt32:
o << int8_t(BinaryConsts::I32And);
break;
case OrInt32:
o << int8_t(BinaryConsts::I32Or);
break;
case XorInt32:
o << int8_t(BinaryConsts::I32Xor);
break;
case ShlInt32:
o << int8_t(BinaryConsts::I32Shl);
break;
case ShrUInt32:
o << int8_t(BinaryConsts::I32ShrU);
break;
case ShrSInt32:
o << int8_t(BinaryConsts::I32ShrS);
break;
case RotLInt32:
o << int8_t(BinaryConsts::I32RotL);
break;
case RotRInt32:
o << int8_t(BinaryConsts::I32RotR);
break;
case EqInt32:
o << int8_t(BinaryConsts::I32Eq);
break;
case NeInt32:
o << int8_t(BinaryConsts::I32Ne);
break;
case LtSInt32:
o << int8_t(BinaryConsts::I32LtS);
break;
case LtUInt32:
o << int8_t(BinaryConsts::I32LtU);
break;
case LeSInt32:
o << int8_t(BinaryConsts::I32LeS);
break;
case LeUInt32:
o << int8_t(BinaryConsts::I32LeU);
break;
case GtSInt32:
o << int8_t(BinaryConsts::I32GtS);
break;
case GtUInt32:
o << int8_t(BinaryConsts::I32GtU);
break;
case GeSInt32:
o << int8_t(BinaryConsts::I32GeS);
break;
case GeUInt32:
o << int8_t(BinaryConsts::I32GeU);
break;
case AddInt64:
o << int8_t(BinaryConsts::I64Add);
break;
case SubInt64:
o << int8_t(BinaryConsts::I64Sub);
break;
case MulInt64:
o << int8_t(BinaryConsts::I64Mul);
break;
case DivSInt64:
o << int8_t(BinaryConsts::I64DivS);
break;
case DivUInt64:
o << int8_t(BinaryConsts::I64DivU);
break;
case RemSInt64:
o << int8_t(BinaryConsts::I64RemS);
break;
case RemUInt64:
o << int8_t(BinaryConsts::I64RemU);
break;
case AndInt64:
o << int8_t(BinaryConsts::I64And);
break;
case OrInt64:
o << int8_t(BinaryConsts::I64Or);
break;
case XorInt64:
o << int8_t(BinaryConsts::I64Xor);
break;
case ShlInt64:
o << int8_t(BinaryConsts::I64Shl);
break;
case ShrUInt64:
o << int8_t(BinaryConsts::I64ShrU);
break;
case ShrSInt64:
o << int8_t(BinaryConsts::I64ShrS);
break;
case RotLInt64:
o << int8_t(BinaryConsts::I64RotL);
break;
case RotRInt64:
o << int8_t(BinaryConsts::I64RotR);
break;
case EqInt64:
o << int8_t(BinaryConsts::I64Eq);
break;
case NeInt64:
o << int8_t(BinaryConsts::I64Ne);
break;
case LtSInt64:
o << int8_t(BinaryConsts::I64LtS);
break;
case LtUInt64:
o << int8_t(BinaryConsts::I64LtU);
break;
case LeSInt64:
o << int8_t(BinaryConsts::I64LeS);
break;
case LeUInt64:
o << int8_t(BinaryConsts::I64LeU);
break;
case GtSInt64:
o << int8_t(BinaryConsts::I64GtS);
break;
case GtUInt64:
o << int8_t(BinaryConsts::I64GtU);
break;
case GeSInt64:
o << int8_t(BinaryConsts::I64GeS);
break;
case GeUInt64:
o << int8_t(BinaryConsts::I64GeU);
break;
case AddFloat32:
o << int8_t(BinaryConsts::F32Add);
break;
case SubFloat32:
o << int8_t(BinaryConsts::F32Sub);
break;
case MulFloat32:
o << int8_t(BinaryConsts::F32Mul);
break;
case DivFloat32:
o << int8_t(BinaryConsts::F32Div);
break;
case CopySignFloat32:
o << int8_t(BinaryConsts::F32CopySign);
break;
case MinFloat32:
o << int8_t(BinaryConsts::F32Min);
break;
case MaxFloat32:
o << int8_t(BinaryConsts::F32Max);
break;
case EqFloat32:
o << int8_t(BinaryConsts::F32Eq);
break;
case NeFloat32:
o << int8_t(BinaryConsts::F32Ne);
break;
case LtFloat32:
o << int8_t(BinaryConsts::F32Lt);
break;
case LeFloat32:
o << int8_t(BinaryConsts::F32Le);
break;
case GtFloat32:
o << int8_t(BinaryConsts::F32Gt);
break;
case GeFloat32:
o << int8_t(BinaryConsts::F32Ge);
break;
case AddFloat64:
o << int8_t(BinaryConsts::F64Add);
break;
case SubFloat64:
o << int8_t(BinaryConsts::F64Sub);
break;
case MulFloat64:
o << int8_t(BinaryConsts::F64Mul);
break;
case DivFloat64:
o << int8_t(BinaryConsts::F64Div);
break;
case CopySignFloat64:
o << int8_t(BinaryConsts::F64CopySign);
break;
case MinFloat64:
o << int8_t(BinaryConsts::F64Min);
break;
case MaxFloat64:
o << int8_t(BinaryConsts::F64Max);
break;
case EqFloat64:
o << int8_t(BinaryConsts::F64Eq);
break;
case NeFloat64:
o << int8_t(BinaryConsts::F64Ne);
break;
case LtFloat64:
o << int8_t(BinaryConsts::F64Lt);
break;
case LeFloat64:
o << int8_t(BinaryConsts::F64Le);
break;
case GtFloat64:
o << int8_t(BinaryConsts::F64Gt);
break;
case GeFloat64:
o << int8_t(BinaryConsts::F64Ge);
break;
case EqVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16Eq);
break;
case NeVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16Ne);
break;
case LtSVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16LtS);
break;
case LtUVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16LtU);
break;
case GtSVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16GtS);
break;
case GtUVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16GtU);
break;
case LeSVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16LeS);
break;
case LeUVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16LeU);
break;
case GeSVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16GeS);
break;
case GeUVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16GeU);
break;
case EqVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8Eq);
break;
case NeVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8Ne);
break;
case LtSVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8LtS);
break;
case LtUVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8LtU);
break;
case GtSVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8GtS);
break;
case GtUVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8GtU);
break;
case LeSVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8LeS);
break;
case LeUVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8LeU);
break;
case GeSVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8GeS);
break;
case GeUVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8GeU);
break;
case EqVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4Eq);
break;
case NeVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4Ne);
break;
case LtSVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4LtS);
break;
case LtUVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4LtU);
break;
case GtSVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4GtS);
break;
case GtUVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4GtU);
break;
case LeSVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4LeS);
break;
case LeUVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4LeU);
break;
case GeSVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4GeS);
break;
case GeUVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4GeU);
break;
case EqVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I64x2Eq);
break;
case NeVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I64x2Ne);
break;
case LtSVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I64x2LtS);
break;
case GtSVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I64x2GtS);
break;
case LeSVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I64x2LeS);
break;
case GeSVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I64x2GeS);
break;
case EqVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Eq);
break;
case NeVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Ne);
break;
case LtVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Lt);
break;
case GtVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Gt);
break;
case LeVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Le);
break;
case GeVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Ge);
break;
case EqVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Eq);
break;
case NeVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Ne);
break;
case LtVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Lt);
break;
case GtVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Gt);
break;
case LeVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Le);
break;
case GeVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Ge);
break;
case EqVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Eq);
break;
case NeVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Ne);
break;
case LtVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Lt);
break;
case GtVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Gt);
break;
case LeVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Le);
break;
case GeVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Ge);
break;
case AndVec128:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::V128And);
break;
case OrVec128:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::V128Or);
break;
case XorVec128:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::V128Xor);
break;
case AndNotVec128:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::V128Andnot);
break;
case AddVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16Add);
break;
case AddSatSVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I8x16AddSatS);
break;
case AddSatUVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I8x16AddSatU);
break;
case SubVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16Sub);
break;
case SubSatSVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I8x16SubSatS);
break;
case SubSatUVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I8x16SubSatU);
break;
case MinSVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16MinS);
break;
case MinUVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16MinU);
break;
case MaxSVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16MaxS);
break;
case MaxUVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16MaxU);
break;
case AvgrUVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I8x16AvgrU);
break;
case AddVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8Add);
break;
case AddSatSVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8AddSatS);
break;
case AddSatUVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8AddSatU);
break;
case SubVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8Sub);
break;
case SubSatSVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8SubSatS);
break;
case SubSatUVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8SubSatU);
break;
case MulVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8Mul);
break;
case MinSVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8MinS);
break;
case MinUVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8MinU);
break;
case MaxSVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8MaxS);
break;
case MaxUVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8MaxU);
break;
case AvgrUVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I16x8AvgrU);
break;
case Q15MulrSatSVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8Q15MulrSatS);
break;
case ExtMulLowSVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8ExtmulLowI8x16S);
break;
case ExtMulHighSVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8ExtmulHighI8x16S);
break;
case ExtMulLowUVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8ExtmulLowI8x16U);
break;
case ExtMulHighUVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8ExtmulHighI8x16U);
break;
case AddVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4Add);
break;
case SubVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4Sub);
break;
case MulVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4Mul);
break;
case MinSVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4MinS);
break;
case MinUVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4MinU);
break;
case MaxSVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4MaxS);
break;
case MaxUVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I32x4MaxU);
break;
case DotSVecI16x8ToVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4DotI16x8S);
break;
case ExtMulLowSVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4ExtmulLowI16x8S);
break;
case ExtMulHighSVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4ExtmulHighI16x8S);
break;
case ExtMulLowUVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4ExtmulLowI16x8U);
break;
case ExtMulHighUVecI32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I32x4ExtmulHighI16x8U);
break;
case AddVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I64x2Add);
break;
case SubVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I64x2Sub);
break;
case MulVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::I64x2Mul);
break;
case ExtMulLowSVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I64x2ExtmulLowI32x4S);
break;
case ExtMulHighSVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I64x2ExtmulHighI32x4S);
break;
case ExtMulLowUVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I64x2ExtmulLowI32x4U);
break;
case ExtMulHighUVecI64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I64x2ExtmulHighI32x4U);
break;
case AddVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Add);
break;
case SubVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Sub);
break;
case MulVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Mul);
break;
case DivVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Div);
break;
case MinVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Min);
break;
case MaxVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Max);
break;
case PMinVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Pmin);
break;
case PMaxVecF16x8:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8Pmax);
break;
case AddVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Add);
break;
case SubVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Sub);
break;
case MulVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Mul);
break;
case DivVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Div);
break;
case MinVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Min);
break;
case MaxVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Max);
break;
case PMinVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Pmin);
break;
case PMaxVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F32x4Pmax);
break;
case AddVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Add);
break;
case SubVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Sub);
break;
case MulVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Mul);
break;
case DivVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Div);
break;
case MinVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Min);
break;
case MaxVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Max);
break;
case PMinVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Pmin);
break;
case PMaxVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F64x2Pmax);
break;
case NarrowSVecI16x8ToVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I8x16NarrowI16x8S);
break;
case NarrowUVecI16x8ToVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I8x16NarrowI16x8U);
break;
case NarrowSVecI32x4ToVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8NarrowI32x4S);
break;
case NarrowUVecI32x4ToVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8NarrowI32x4U);
break;
case SwizzleVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I8x16Swizzle);
break;
case RelaxedSwizzleVecI8x16:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I8x16RelaxedSwizzle);
break;
case RelaxedMinVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::F32x4RelaxedMin);
break;
case RelaxedMaxVecF32x4:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::F32x4RelaxedMax);
break;
case RelaxedMinVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::F64x2RelaxedMin);
break;
case RelaxedMaxVecF64x2:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::F64x2RelaxedMax);
break;
case RelaxedQ15MulrSVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8RelaxedQ15MulrS);
break;
case DotI8x16I7x16SToVecI16x8:
o << int8_t(BinaryConsts::SIMDPrefix)
<< U32LEB(BinaryConsts::I16x8DotI8x16I7x16S);
break;
case InvalidBinary:
WASM_UNREACHABLE("invalid binary op");
}
}
void BinaryInstWriter::visitSelect(Select* curr) {
if (curr->type.isRef()) {
o << int8_t(BinaryConsts::SelectWithType) << U32LEB(curr->type.size());
for (size_t i = 0; i < curr->type.size(); i++) {
parent.writeType(curr->type != Type::unreachable ? curr->type
: Type::none);
}
} else {
o << int8_t(BinaryConsts::Select);
}
}
void BinaryInstWriter::visitReturn(Return* curr) {
o << int8_t(BinaryConsts::Return);
}
void BinaryInstWriter::visitMemorySize(MemorySize* curr) {
o << int8_t(BinaryConsts::MemorySize);
o << U32LEB(parent.getMemoryIndex(curr->memory));
}
void BinaryInstWriter::visitMemoryGrow(MemoryGrow* curr) {
o << int8_t(BinaryConsts::MemoryGrow);
o << U32LEB(parent.getMemoryIndex(curr->memory));
}
void BinaryInstWriter::visitRefNull(RefNull* curr) {
o << int8_t(BinaryConsts::RefNull);
parent.writeHeapType(curr->type.getHeapType());
}
void BinaryInstWriter::visitRefIsNull(RefIsNull* curr) {
o << int8_t(BinaryConsts::RefIsNull);
}
void BinaryInstWriter::visitRefFunc(RefFunc* curr) {
o << int8_t(BinaryConsts::RefFunc)
<< U32LEB(parent.getFunctionIndex(curr->func));
}
void BinaryInstWriter::visitRefEq(RefEq* curr) {
o << int8_t(BinaryConsts::RefEq);
}
void BinaryInstWriter::visitTableGet(TableGet* curr) {
o << int8_t(BinaryConsts::TableGet);
o << U32LEB(parent.getTableIndex(curr->table));
}
void BinaryInstWriter::visitTableSet(TableSet* curr) {
o << int8_t(BinaryConsts::TableSet);
o << U32LEB(parent.getTableIndex(curr->table));
}
void BinaryInstWriter::visitTableSize(TableSize* curr) {
o << int8_t(BinaryConsts::MiscPrefix) << U32LEB(BinaryConsts::TableSize);
o << U32LEB(parent.getTableIndex(curr->table));
}
void BinaryInstWriter::visitTableGrow(TableGrow* curr) {
o << int8_t(BinaryConsts::MiscPrefix) << U32LEB(BinaryConsts::TableGrow);
o << U32LEB(parent.getTableIndex(curr->table));
}
void BinaryInstWriter::visitTableFill(TableFill* curr) {
o << int8_t(BinaryConsts::MiscPrefix) << U32LEB(BinaryConsts::TableFill);
o << U32LEB(parent.getTableIndex(curr->table));
}
void BinaryInstWriter::visitTableCopy(TableCopy* curr) {
o << int8_t(BinaryConsts::MiscPrefix) << U32LEB(BinaryConsts::TableCopy);
o << U32LEB(parent.getTableIndex(curr->destTable));
o << U32LEB(parent.getTableIndex(curr->sourceTable));
}
void BinaryInstWriter::visitTableInit(TableInit* curr) {
o << int8_t(BinaryConsts::MiscPrefix) << U32LEB(BinaryConsts::TableInit);
o << U32LEB(parent.getElementSegmentIndex(curr->segment));
o << U32LEB(parent.getTableIndex(curr->table));
}
void BinaryInstWriter::visitTry(Try* curr) {
breakStack.push_back(curr->name);
o << int8_t(BinaryConsts::Try);
emitResultType(curr->type);
}
void BinaryInstWriter::visitTryTable(TryTable* curr) {
o << int8_t(BinaryConsts::TryTable);
emitResultType(curr->type);
o << U32LEB(curr->catchTags.size());
for (Index i = 0; i < curr->catchTags.size(); i++) {
if (curr->catchTags[i]) {
o << (curr->catchRefs[i] ? int8_t(BinaryConsts::CatchRef)
: int8_t(BinaryConsts::Catch));
o << U32LEB(parent.getTagIndex(curr->catchTags[i]));
} else {
o << (curr->catchRefs[i] ? int8_t(BinaryConsts::CatchAllRef)
: int8_t(BinaryConsts::CatchAll));
}
o << U32LEB(getBreakIndex(curr->catchDests[i]));
}
// the binary format requires this; we have a block if we need one
// catch_*** clauses should refer to block labels without entering the try
// scope. So we do this at the end.
breakStack.emplace_back(IMPOSSIBLE_CONTINUE);
}
void BinaryInstWriter::emitCatch(Try* curr, Index i) {
if (func && !sourceMap) {
parent.writeExtraDebugLocation(curr, func, i);
}
o << int8_t(BinaryConsts::Catch_Legacy)
<< U32LEB(parent.getTagIndex(curr->catchTags[i]));
}
void BinaryInstWriter::emitCatchAll(Try* curr) {
if (func && !sourceMap) {
parent.writeExtraDebugLocation(curr, func, curr->catchBodies.size());
}
o << int8_t(BinaryConsts::CatchAll_Legacy);
}
void BinaryInstWriter::emitDelegate(Try* curr) {
// The delegate ends the scope in effect, and pops the try's name. Note that
// the getBreakIndex is intentionally after that pop, as the delegate cannot
// target its own try.
assert(!breakStack.empty());
breakStack.pop_back();
o << int8_t(BinaryConsts::Delegate)
<< U32LEB(getBreakIndex(curr->delegateTarget));
}
void BinaryInstWriter::visitThrow(Throw* curr) {
o << int8_t(BinaryConsts::Throw) << U32LEB(parent.getTagIndex(curr->tag));
}
void BinaryInstWriter::visitRethrow(Rethrow* curr) {
o << int8_t(BinaryConsts::Rethrow) << U32LEB(getBreakIndex(curr->target));
}
void BinaryInstWriter::visitThrowRef(ThrowRef* curr) {
o << int8_t(BinaryConsts::ThrowRef);
}
void BinaryInstWriter::visitNop(Nop* curr) { o << int8_t(BinaryConsts::Nop); }
void BinaryInstWriter::visitUnreachable(Unreachable* curr) {
o << int8_t(BinaryConsts::Unreachable);
}
void BinaryInstWriter::visitDrop(Drop* curr) {
size_t numValues = curr->value->type.size();
for (size_t i = 0; i < numValues; i++) {
o << int8_t(BinaryConsts::Drop);
}
}
void BinaryInstWriter::visitPop(Pop* curr) {
// Turns into nothing in the binary format
}
void BinaryInstWriter::visitTupleMake(TupleMake* curr) {
// Turns into nothing in the binary format
}
void BinaryInstWriter::visitTupleExtract(TupleExtract* curr) {
if (extractedGets.count(curr->tuple)) {
// We already have just the extracted value on the stack.
return;
}
size_t numVals = curr->tuple->type.size();
// Drop all values after the one we want
for (size_t i = curr->index + 1; i < numVals; ++i) {
o << int8_t(BinaryConsts::Drop);
}
// If the extracted value is the only one left, we're done
if (curr->index == 0) {
return;
}
// Otherwise, save it to a scratch local, drop the others, then retrieve it
assert(scratchLocals.find(curr->type) != scratchLocals.end());
auto scratch = scratchLocals[curr->type];
o << int8_t(BinaryConsts::LocalSet) << U32LEB(scratch);
for (size_t i = 0; i < curr->index; ++i) {
o << int8_t(BinaryConsts::Drop);
}
o << int8_t(BinaryConsts::LocalGet) << U32LEB(scratch);
}
void BinaryInstWriter::visitRefI31(RefI31* curr) {
o << int8_t(BinaryConsts::GCPrefix)
<< U32LEB(curr->type.getHeapType().isShared() ? BinaryConsts::RefI31Shared
: BinaryConsts::RefI31);
}
void BinaryInstWriter::visitI31Get(I31Get* curr) {
o << int8_t(BinaryConsts::GCPrefix)
<< U32LEB(curr->signed_ ? BinaryConsts::I31GetS : BinaryConsts::I31GetU);
}
void BinaryInstWriter::visitCallRef(CallRef* curr) {
assert(curr->target->type != Type::unreachable);
if (curr->target->type.isNull()) {
emitUnreachable();
return;
}
o << int8_t(curr->isReturn ? BinaryConsts::RetCallRef
: BinaryConsts::CallRef);
parent.writeIndexedHeapType(curr->target->type.getHeapType());
}
void BinaryInstWriter::visitRefTest(RefTest* curr) {
o << int8_t(BinaryConsts::GCPrefix);
if (curr->castType.isNullable()) {
o << U32LEB(BinaryConsts::RefTestNull);
} else {
o << U32LEB(BinaryConsts::RefTest);
}
parent.writeHeapType(curr->castType.getHeapType());
}
void BinaryInstWriter::visitRefCast(RefCast* curr) {
o << int8_t(BinaryConsts::GCPrefix);
if (curr->type.isNullable()) {
o << U32LEB(BinaryConsts::RefCastNull);
} else {
o << U32LEB(BinaryConsts::RefCast);
}
parent.writeHeapType(curr->type.getHeapType());
}
void BinaryInstWriter::visitBrOn(BrOn* curr) {
switch (curr->op) {
case BrOnNull:
o << int8_t(BinaryConsts::BrOnNull);
o << U32LEB(getBreakIndex(curr->name));
return;
case BrOnNonNull:
o << int8_t(BinaryConsts::BrOnNonNull);
o << U32LEB(getBreakIndex(curr->name));
return;
case BrOnCast:
case BrOnCastFail: {
o << int8_t(BinaryConsts::GCPrefix);
if (curr->op == BrOnCast) {
o << U32LEB(BinaryConsts::BrOnCast);
} else {
o << U32LEB(BinaryConsts::BrOnCastFail);
}
assert(curr->ref->type.isRef());
assert(Type::isSubType(curr->castType, curr->ref->type));
uint8_t flags = (curr->ref->type.isNullable() ? 1 : 0) |
(curr->castType.isNullable() ? 2 : 0);
o << flags;
o << U32LEB(getBreakIndex(curr->name));
parent.writeHeapType(curr->ref->type.getHeapType());
parent.writeHeapType(curr->castType.getHeapType());
return;
}
}
WASM_UNREACHABLE("invalid br_on_*");
}
void BinaryInstWriter::visitStructNew(StructNew* curr) {
o << int8_t(BinaryConsts::GCPrefix);
if (curr->isWithDefault()) {
o << U32LEB(BinaryConsts::StructNewDefault);
} else {
o << U32LEB(BinaryConsts::StructNew);
}
parent.writeIndexedHeapType(curr->type.getHeapType());
}
void BinaryInstWriter::visitStructGet(StructGet* curr) {
if (curr->ref->type.isNull()) {
emitUnreachable();
return;
}
const auto& heapType = curr->ref->type.getHeapType();
const auto& field = heapType.getStruct().fields[curr->index];
int8_t op;
if (field.type != Type::i32 || field.packedType == Field::not_packed) {
op = BinaryConsts::StructGet;
} else if (curr->signed_) {
op = BinaryConsts::StructGetS;
} else {
op = BinaryConsts::StructGetU;
}
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(op);
parent.writeIndexedHeapType(heapType);
o << U32LEB(curr->index);
}
void BinaryInstWriter::visitStructSet(StructSet* curr) {
if (curr->ref->type.isNull()) {
emitUnreachable();
return;
}
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::StructSet);
parent.writeIndexedHeapType(curr->ref->type.getHeapType());
o << U32LEB(curr->index);
}
void BinaryInstWriter::visitArrayNew(ArrayNew* curr) {
o << int8_t(BinaryConsts::GCPrefix);
if (curr->isWithDefault()) {
o << U32LEB(BinaryConsts::ArrayNewDefault);
} else {
o << U32LEB(BinaryConsts::ArrayNew);
}
parent.writeIndexedHeapType(curr->type.getHeapType());
}
void BinaryInstWriter::visitArrayNewData(ArrayNewData* curr) {
o << int8_t(BinaryConsts::GCPrefix);
o << U32LEB(BinaryConsts::ArrayNewData);
parent.writeIndexedHeapType(curr->type.getHeapType());
o << U32LEB(parent.getDataSegmentIndex(curr->segment));
}
void BinaryInstWriter::visitArrayNewElem(ArrayNewElem* curr) {
o << int8_t(BinaryConsts::GCPrefix);
o << U32LEB(BinaryConsts::ArrayNewElem);
parent.writeIndexedHeapType(curr->type.getHeapType());
o << U32LEB(parent.getElementSegmentIndex(curr->segment));
}
void BinaryInstWriter::visitArrayNewFixed(ArrayNewFixed* curr) {
o << int8_t(BinaryConsts::GCPrefix);
o << U32LEB(BinaryConsts::ArrayNewFixed);
parent.writeIndexedHeapType(curr->type.getHeapType());
o << U32LEB(curr->values.size());
}
void BinaryInstWriter::visitArrayGet(ArrayGet* curr) {
if (curr->ref->type.isNull()) {
emitUnreachable();
return;
}
auto heapType = curr->ref->type.getHeapType();
const auto& field = heapType.getArray().element;
int8_t op;
if (field.type != Type::i32 || field.packedType == Field::not_packed) {
op = BinaryConsts::ArrayGet;
} else if (curr->signed_) {
op = BinaryConsts::ArrayGetS;
} else {
op = BinaryConsts::ArrayGetU;
}
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(op);
parent.writeIndexedHeapType(heapType);
}
void BinaryInstWriter::visitArraySet(ArraySet* curr) {
if (curr->ref->type.isNull()) {
emitUnreachable();
return;
}
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::ArraySet);
parent.writeIndexedHeapType(curr->ref->type.getHeapType());
}
void BinaryInstWriter::visitArrayLen(ArrayLen* curr) {
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::ArrayLen);
}
void BinaryInstWriter::visitArrayCopy(ArrayCopy* curr) {
if (curr->srcRef->type.isNull() || curr->destRef->type.isNull()) {
emitUnreachable();
return;
}
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::ArrayCopy);
parent.writeIndexedHeapType(curr->destRef->type.getHeapType());
parent.writeIndexedHeapType(curr->srcRef->type.getHeapType());
}
void BinaryInstWriter::visitArrayFill(ArrayFill* curr) {
if (curr->ref->type.isNull()) {
emitUnreachable();
return;
}
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::ArrayFill);
parent.writeIndexedHeapType(curr->ref->type.getHeapType());
}
void BinaryInstWriter::visitArrayInitData(ArrayInitData* curr) {
if (curr->ref->type.isNull()) {
emitUnreachable();
return;
}
o << int8_t(BinaryConsts::GCPrefix);
o << U32LEB(BinaryConsts::ArrayInitData);
parent.writeIndexedHeapType(curr->ref->type.getHeapType());
o << U32LEB(parent.getDataSegmentIndex(curr->segment));
}
void BinaryInstWriter::visitArrayInitElem(ArrayInitElem* curr) {
if (curr->ref->type.isNull()) {
emitUnreachable();
return;
}
o << int8_t(BinaryConsts::GCPrefix);
o << U32LEB(BinaryConsts::ArrayInitElem);
parent.writeIndexedHeapType(curr->ref->type.getHeapType());
o << U32LEB(parent.getElementSegmentIndex(curr->segment));
}
void BinaryInstWriter::visitRefAs(RefAs* curr) {
switch (curr->op) {
case RefAsNonNull:
o << int8_t(BinaryConsts::RefAsNonNull);
break;
case AnyConvertExtern:
o << int8_t(BinaryConsts::GCPrefix)
<< U32LEB(BinaryConsts::AnyConvertExtern);
break;
case ExternConvertAny:
o << int8_t(BinaryConsts::GCPrefix)
<< U32LEB(BinaryConsts::ExternConvertAny);
break;
default:
WASM_UNREACHABLE("invalid ref.as_*");
}
}
void BinaryInstWriter::visitStringNew(StringNew* curr) {
if (curr->ref->type.isNull()) {
// This is a bottom type, so this is an array-receiving operation that does
// not receive an array. The spec allows this, but V8 does not, see
// https://github.com/WebAssembly/stringref/issues/66
// For now, just emit an unreachable here as this will definitely trap.
emitUnreachable();
return;
}
o << int8_t(BinaryConsts::GCPrefix);
switch (curr->op) {
case StringNewLossyUTF8Array:
o << U32LEB(BinaryConsts::StringNewLossyUTF8Array);
break;
case StringNewWTF16Array:
o << U32LEB(BinaryConsts::StringNewWTF16Array);
break;
case StringNewFromCodePoint:
o << U32LEB(BinaryConsts::StringFromCodePoint);
break;
default:
WASM_UNREACHABLE("invalid string.new*");
}
}
void BinaryInstWriter::visitStringConst(StringConst* curr) {
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::StringConst)
<< U32LEB(parent.getStringIndex(curr->string));
}
void BinaryInstWriter::visitStringMeasure(StringMeasure* curr) {
o << int8_t(BinaryConsts::GCPrefix);
switch (curr->op) {
case StringMeasureUTF8:
o << U32LEB(BinaryConsts::StringMeasureUTF8);
break;
case StringMeasureWTF16:
o << U32LEB(BinaryConsts::StringMeasureWTF16);
break;
default:
WASM_UNREACHABLE("invalid string.new*");
}
}
void BinaryInstWriter::visitStringEncode(StringEncode* curr) {
if (curr->str->type.isNull()) {
// See visitStringNew.
emitUnreachable();
return;
}
o << int8_t(BinaryConsts::GCPrefix);
switch (curr->op) {
case StringEncodeLossyUTF8Array:
o << U32LEB(BinaryConsts::StringEncodeLossyUTF8Array);
break;
case StringEncodeWTF16Array:
o << U32LEB(BinaryConsts::StringEncodeWTF16Array);
break;
default:
WASM_UNREACHABLE("invalid string.new*");
}
}
void BinaryInstWriter::visitStringConcat(StringConcat* curr) {
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::StringConcat);
}
void BinaryInstWriter::visitStringEq(StringEq* curr) {
o << int8_t(BinaryConsts::GCPrefix);
switch (curr->op) {
case StringEqEqual:
o << U32LEB(BinaryConsts::StringEq);
break;
case StringEqCompare:
o << U32LEB(BinaryConsts::StringCompare);
break;
default:
WASM_UNREACHABLE("invalid string.eq*");
}
}
void BinaryInstWriter::visitStringWTF16Get(StringWTF16Get* curr) {
// We need to convert the ref operand to a stringview, but it is under the pos
// operand. Put the i32 in a scratch local, emit the conversion, then get the
// i32 back onto the stack. If `pos` is a local.get anyway, then we can skip
// the scratch local.
bool posDeferred = false;
Index posIndex;
if (auto* get = curr->pos->dynCast<LocalGet>()) {
assert(deferredGets.count(get));
posDeferred = true;
posIndex = mappedLocals[{get->index, 0}];
} else {
posIndex = scratchLocals[Type::i32];
}
if (!posDeferred) {
o << int8_t(BinaryConsts::LocalSet) << U32LEB(posIndex);
}
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::StringAsWTF16);
o << int8_t(BinaryConsts::LocalGet) << U32LEB(posIndex);
o << int8_t(BinaryConsts::GCPrefix)
<< U32LEB(BinaryConsts::StringViewWTF16GetCodePoint);
}
void BinaryInstWriter::visitStringSliceWTF(StringSliceWTF* curr) {
// We need to convert the ref operand to a stringview, but it is buried under
// the start and end operands. Put the i32s in scratch locals, emit the
// conversion, then get the i32s back onto the stack. If both `start` and
// `end` are already local.gets, then we can skip the scratch locals.
bool deferred = false;
Index startIndex, endIndex;
auto* startGet = curr->start->dynCast<LocalGet>();
auto* endGet = curr->end->dynCast<LocalGet>();
if (startGet && endGet) {
assert(deferredGets.count(startGet));
assert(deferredGets.count(endGet));
deferred = true;
startIndex = mappedLocals[{startGet->index, 0}];
endIndex = mappedLocals[{endGet->index, 0}];
} else {
startIndex = scratchLocals[Type::i32];
endIndex = startIndex + 1;
}
if (!deferred) {
o << int8_t(BinaryConsts::LocalSet) << U32LEB(endIndex);
o << int8_t(BinaryConsts::LocalSet) << U32LEB(startIndex);
}
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::StringAsWTF16);
o << int8_t(BinaryConsts::LocalGet) << U32LEB(startIndex);
o << int8_t(BinaryConsts::LocalGet) << U32LEB(endIndex);
o << int8_t(BinaryConsts::GCPrefix)
<< U32LEB(BinaryConsts::StringViewWTF16Slice);
}
void BinaryInstWriter::visitContBind(ContBind* curr) {
o << int8_t(BinaryConsts::ContBind);
parent.writeIndexedHeapType(curr->contTypeBefore);
parent.writeIndexedHeapType(curr->contTypeAfter);
}
void BinaryInstWriter::visitContNew(ContNew* curr) {
o << int8_t(BinaryConsts::ContNew);
parent.writeIndexedHeapType(curr->contType);
}
void BinaryInstWriter::visitResume(Resume* curr) {
o << int8_t(BinaryConsts::Resume);
parent.writeIndexedHeapType(curr->contType);
size_t handlerNum = curr->handlerTags.size();
o << U32LEB(handlerNum);
for (size_t i = 0; i < handlerNum; i++) {
o << U32LEB(parent.getTagIndex(curr->handlerTags[i]))
<< U32LEB(getBreakIndex(curr->handlerBlocks[i]));
}
}
void BinaryInstWriter::visitSuspend(Suspend* curr) {
o << int8_t(BinaryConsts::Suspend) << U32LEB(parent.getTagIndex(curr->tag));
}
void BinaryInstWriter::emitScopeEnd(Expression* curr) {
assert(!breakStack.empty());
breakStack.pop_back();
o << int8_t(BinaryConsts::End);
if (func && !sourceMap) {
parent.writeDebugLocationEnd(curr, func);
}
}
void BinaryInstWriter::emitFunctionEnd() { o << int8_t(BinaryConsts::End); }
void BinaryInstWriter::emitUnreachable() {
o << int8_t(BinaryConsts::Unreachable);
}
void BinaryInstWriter::mapLocalsAndEmitHeader() {
assert(func && "BinaryInstWriter: function is not set");
// Map params
for (Index i = 0; i < func->getNumParams(); i++) {
mappedLocals[std::make_pair(i, 0)] = i;
}
auto scratches = countScratchLocals();
// Normally we map all locals of the same type into a range of adjacent
// addresses, which is more compact. However, if we need to keep DWARF valid,
// do not do any reordering at all - instead, do a trivial mapping that
// keeps everything unmoved.
//
// Unless we have run DWARF-invalidating passes, all locals added during the
// process that are not in DWARF info (tuple locals, tuple scratch locals,
// locals to resolve stacky format, ..) have been all tacked on to the
// existing locals and happen at the end, so as long as we print the local
// types in order, we don't invalidate original local DWARF info here.
if (DWARF) {
Index mappedIndex = func->getVarIndexBase();
for (Index i = func->getVarIndexBase(); i < func->getNumLocals(); i++) {
size_t size = func->getLocalType(i).size();
for (Index j = 0; j < size; j++) {
mappedLocals[std::make_pair(i, j)] = mappedIndex++;
}
}
size_t numBinaryLocals =
mappedIndex - func->getVarIndexBase() + scratches.size();
o << U32LEB(numBinaryLocals);
for (Index i = func->getVarIndexBase(); i < func->getNumLocals(); i++) {
for (const auto& type : func->getLocalType(i)) {
o << U32LEB(1);
parent.writeType(type);
}
}
for (auto& [type, count] : scratches) {
o << U32LEB(count);
parent.writeType(type);
scratchLocals[type] = mappedIndex;
mappedIndex += count;
}
return;
}
for (auto type : func->vars) {
for (const auto& t : type) {
noteLocalType(t);
}
}
for (auto& [type, count] : scratches) {
noteLocalType(type, count);
}
if (parent.getModule()->features.hasReferenceTypes()) {
// Sort local types in a way that keeps all MVP types together and all
// reference types together. E.g. it is helpful to avoid a block of i32s in
// between blocks of different reference types, since clearing out reference
// types may require different work.
//
// See https://github.com/WebAssembly/binaryen/issues/4773
//
// In order to decide whether to put MVP types or reference types first,
// look at the type of the first local. In an optimized binary we will have
// sorted the locals by frequency of uses, so this way we'll keep the most
// commonly-used local at the top, which should work well in many cases.
bool refsFirst = !localTypes.empty() && localTypes[0].isRef();
std::stable_sort(localTypes.begin(), localTypes.end(), [&](Type a, Type b) {
if (refsFirst) {
return a.isRef() && !b.isRef();
} else {
return !a.isRef() && b.isRef();
}
});
}
// Map IR (local index, tuple index) pairs to binary local indices. Since
// locals are grouped by type, start by calculating the base indices for each
// type.
std::unordered_map<Type, Index> nextFreeIndex;
Index baseIndex = func->getVarIndexBase();
for (auto& type : localTypes) {
nextFreeIndex[type] = baseIndex;
baseIndex += numLocalsByType[type];
}
// Map the IR index pairs to indices.
for (Index i = func->getVarIndexBase(); i < func->getNumLocals(); i++) {
Index j = 0;
for (const auto& type : func->getLocalType(i)) {
mappedLocals[{i, j++}] = nextFreeIndex[type]++;
}
}
// Map scratch locals to the remaining indices.
for (auto& [type, _] : scratches) {
scratchLocals[type] = nextFreeIndex[type];
}
o << U32LEB(numLocalsByType.size());
for (auto& localType : localTypes) {
o << U32LEB(numLocalsByType.at(localType));
parent.writeType(localType);
}
}
void BinaryInstWriter::noteLocalType(Type type, Index count) {
auto& num = numLocalsByType[type];
if (num == 0) {
localTypes.push_back(type);
}
num += count;
}
InsertOrderedMap<Type, Index> BinaryInstWriter::countScratchLocals() {
struct ScratchLocalFinder : PostWalker<ScratchLocalFinder> {
BinaryInstWriter& parent;
InsertOrderedMap<Type, Index> scratches;
ScratchLocalFinder(BinaryInstWriter& parent) : parent(parent) {}
void visitTupleExtract(TupleExtract* curr) {
if (curr->type == Type::unreachable) {
// We will not emit this instruction anyway.
return;
}
// Extracts from locals or globals are optimizable and do not require
// scratch locals. Record them.
auto* tuple = curr->tuple;
if (tuple->is<LocalGet>() || tuple->is<LocalSet>() ||
tuple->is<GlobalGet>()) {
parent.extractedGets.insert({tuple, curr->index});
return;
}
// Include a scratch register for each type of tuple.extract with nonzero
// index present.
if (curr->index != 0) {
auto& count = scratches[curr->type];
count = std::max(count, 1u);
}
}
void visitStringWTF16Get(StringWTF16Get* curr) {
if (curr->type == Type::unreachable) {
return;
}
// If `pos` already a local.get, we can defer emitting that local.get
// instead of using a scratch local.
if (auto* get = curr->pos->dynCast<LocalGet>()) {
parent.deferredGets.insert(get);
return;
}
// Scratch local to hold the `pos` value while we emit a stringview
// conversion for the `ref` value.
auto& count = scratches[Type::i32];
count = std::max(count, 1u);
}
void visitStringSliceWTF(StringSliceWTF* curr) {
if (curr->type == Type::unreachable) {
return;
}
// If `start` and `end` are already local.gets, we can defer emitting
// those gets instead of using scratch locals.
auto* startGet = curr->start->dynCast<LocalGet>();
auto* endGet = curr->end->dynCast<LocalGet>();
if (startGet && endGet) {
parent.deferredGets.insert(startGet);
parent.deferredGets.insert(endGet);
return;
}
// Scratch locals to hold the `start` and `end` values while we emit a
// stringview conversion for the `ref` value.
auto& count = scratches[Type::i32];
count = std::max(count, 2u);
}
// As mentioned in BinaryInstWriter::visitBreak, the type of br_if with a
// value may be more refined in Binaryen IR compared to the wasm spec, as we
// give it the type of the value, while the spec gives it the type of the
// block it targets. To avoid problems we must handle the case where a br_if
// has a value, the value is more refined then the target, and the value is
// not dropped (the last condition is very rare in real-world wasm, making
// all of this a quite unusual situation). First, detect such situations by
// seeing if we have br_ifs that return reference types at all. We do so by
// counting them, and as we go we ignore ones that are dropped, since a
// dropped value is not a problem for us.
//
// Note that we do not check all the conditions here, such as if the type
// matches the break target, or if the parent is a cast, which we leave for
// a more expensive analysis later, which we only run if we see something
// suspicious here.
Index numDangerousBrIfs = 0;
void visitBreak(Break* curr) {
if (curr->type.hasRef()) {
numDangerousBrIfs++;
}
}
void visitDrop(Drop* curr) {
if (curr->value->is<Break>() && curr->value->type.hasRef()) {
// The value is exactly a br_if of a ref, that we just visited before
// us. Undo the ++ from there as it can be ignored.
assert(numDangerousBrIfs > 0);
numDangerousBrIfs--;
}
}
} finder(*this);
finder.walk(func->body);
if (!finder.numDangerousBrIfs || !parent.getModule()->features.hasGC()) {
// Nothing more to do: either no such br_ifs, or GC is not enabled.
//
// The explicit check for GC is here because if only reference types are
// enabled then we still may seem to need a fixup here, e.g. if a ref.func
// is br_if'd to a block of type funcref. But that only appears that way
// because in Binaryen IR we allow non-nullable types even without GC (and
// if GC is not enabled then we always emit nullable types in the binary).
// That is, even if we see a type difference without GC, it will vanish in
// the binary format; there is never a need to add any ref.casts without GC
// being enabled.
return std::move(finder.scratches);
}
// There are dangerous-looking br_ifs, so we must do the harder work to
// actually investigate them in detail, including tracking block types. By
// being fully precise here, we'll only emit casts when absolutely necessary,
// which avoids repeated roundtrips adding more and more code.
struct RefinementScanner : public ExpressionStackWalker<RefinementScanner> {
BinaryInstWriter& writer;
ScratchLocalFinder& finder;
RefinementScanner(BinaryInstWriter& writer, ScratchLocalFinder& finder)
: writer(writer), finder(finder) {}
void visitBreak(Break* curr) {
// See if this is one of the dangerous br_ifs we must handle.
if (!curr->type.hasRef()) {
// Not even a reference.
return;
}
auto* parent = getParent();
if (parent) {
if (parent->is<Drop>()) {
// It is dropped anyhow.
return;
}
if (auto* cast = parent->dynCast<RefCast>()) {
if (Type::isSubType(cast->type, curr->type)) {
// It is cast to the same type or a better one. In particular this
// handles the case of repeated roundtripping: After the first
// roundtrip we emit a cast that we'll identify here, and not emit
// an additional one.
return;
}
}
}
auto* breakTarget = findBreakTarget(curr->name);
auto unrefinedType = breakTarget->type;
if (unrefinedType == curr->type) {
// It has the proper type anyhow.
return;
}
// Mark the br_if as needing handling, and add the type to the set of
// types we need scratch tuple locals for (if relevant).
writer.brIfsNeedingHandling[curr] = unrefinedType;
if (unrefinedType.isTuple()) {
// We must allocate enough scratch locals for this tuple. Note that we
// may need more than one per type in the tuple, if a type appears more
// than once, so we count their appearances.
InsertOrderedMap<Type, Index> scratchTypeUses;
for (auto t : unrefinedType) {
scratchTypeUses[t]++;
}
for (auto& [type, uses] : scratchTypeUses) {
auto& count = finder.scratches[type];
count = std::max(count, uses);
}
}
}
} refinementScanner(*this, finder);
refinementScanner.walk(func->body);
return std::move(finder.scratches);
}
void BinaryInstWriter::emitMemoryAccess(size_t alignment,
size_t bytes,
uint64_t offset,
Name memory) {
uint32_t alignmentBits = Bits::log2(alignment ? alignment : bytes);
uint32_t memoryIdx = parent.getMemoryIndex(memory);
if (memoryIdx > 0) {
// Set bit 6 in the alignment to indicate a memory index is present per:
// https://github.com/WebAssembly/multi-memory/blob/main/proposals/multi-memory/Overview.md
alignmentBits = alignmentBits | 1 << 6;
}
o << U32LEB(alignmentBits);
if (memoryIdx > 0) {
o << U32LEB(memoryIdx);
}
bool memory64 = parent.getModule()->getMemory(memory)->is64();
if (memory64) {
o << U64LEB(offset);
} else {
o << U32LEB(offset);
}
}
int32_t BinaryInstWriter::getBreakIndex(Name name) { // -1 if not found
if (name == DELEGATE_CALLER_TARGET) {
return breakStack.size();
}
for (int i = breakStack.size() - 1; i >= 0; i--) {
if (breakStack[i] == name) {
return breakStack.size() - 1 - i;
}
}
WASM_UNREACHABLE("break index not found");
}
// Queues the expressions linearly in Stack IR (SIR)
class StackIRGenerator : public BinaryenIRWriter<StackIRGenerator> {
public:
StackIRGenerator(Module& module, Function* func)
: BinaryenIRWriter<StackIRGenerator>(func), module(module) {}
void emit(Expression* curr);
void emitScopeEnd(Expression* curr);
void emitHeader() {}
void emitIfElse(If* curr) {
stackIR.push_back(makeStackInst(StackInst::IfElse, curr));
}
void emitCatch(Try* curr, Index i) {
stackIR.push_back(makeStackInst(StackInst::Catch, curr));
}
void emitCatchAll(Try* curr) {
stackIR.push_back(makeStackInst(StackInst::CatchAll, curr));
}
void emitDelegate(Try* curr) {
stackIR.push_back(makeStackInst(StackInst::Delegate, curr));
}
void emitFunctionEnd() {}
void emitUnreachable() {
stackIR.push_back(makeStackInst(Builder(module).makeUnreachable()));
}
void emitDebugLocation(Expression* curr) {}
StackIR& getStackIR() { return stackIR; }
private:
StackInst* makeStackInst(StackInst::Op op, Expression* origin);
StackInst* makeStackInst(Expression* origin) {
return makeStackInst(StackInst::Basic, origin);
}
Module& module;
StackIR stackIR; // filled in write()
};
void StackIRGenerator::emit(Expression* curr) {
StackInst* stackInst = nullptr;
if (curr->is<Block>()) {
stackInst = makeStackInst(StackInst::BlockBegin, curr);
} else if (curr->is<If>()) {
stackInst = makeStackInst(StackInst::IfBegin, curr);
} else if (curr->is<Loop>()) {
stackInst = makeStackInst(StackInst::LoopBegin, curr);
} else if (curr->is<Try>()) {
stackInst = makeStackInst(StackInst::TryBegin, curr);
} else if (curr->is<TryTable>()) {
stackInst = makeStackInst(StackInst::TryTableBegin, curr);
} else {
stackInst = makeStackInst(curr);
}
stackIR.push_back(stackInst);
}
void StackIRGenerator::emitScopeEnd(Expression* curr) {
StackInst* stackInst = nullptr;
if (curr->is<Block>()) {
stackInst = makeStackInst(StackInst::BlockEnd, curr);
} else if (curr->is<If>()) {
stackInst = makeStackInst(StackInst::IfEnd, curr);
} else if (curr->is<Loop>()) {
stackInst = makeStackInst(StackInst::LoopEnd, curr);
} else if (curr->is<Try>()) {
stackInst = makeStackInst(StackInst::TryEnd, curr);
} else if (curr->is<TryTable>()) {
stackInst = makeStackInst(StackInst::TryTableEnd, curr);
} else {
WASM_UNREACHABLE("unexpected expr type");
}
stackIR.push_back(stackInst);
}
StackInst* StackIRGenerator::makeStackInst(StackInst::Op op,
Expression* origin) {
auto* ret = module.allocator.alloc<StackInst>();
ret->op = op;
ret->origin = origin;
auto stackType = origin->type;
if (Properties::isControlFlowStructure(origin)) {
if (stackType == Type::unreachable) {
// There are no unreachable blocks, loops, ifs, trys, or try_tables. we
// emit extra unreachables to fix that up, so that they are valid as
// having none type.
stackType = Type::none;
} else if (op != StackInst::BlockEnd && op != StackInst::IfEnd &&
op != StackInst::LoopEnd && op != StackInst::TryEnd &&
op != StackInst::TryTableEnd) {
// If a concrete type is returned, we mark the end of the construct has
// having that type (as it is pushed to the value stack at that point),
// other parts are marked as none).
stackType = Type::none;
}
}
ret->type = stackType;
return ret;
}
ModuleStackIR::ModuleStackIR(Module& wasm, const PassOptions& options)
: analysis(wasm, [&](Function* func, StackIR& stackIR) {
if (func->imported()) {
return;
}
StackIRGenerator stackIRGen(wasm, func);
stackIRGen.write();
stackIR = std::move(stackIRGen.getStackIR());
if (options.optimizeStackIR) {
StackIROptimizer optimizer(func, stackIR, options, wasm.features);
optimizer.run();
}
}) {}
void StackIRToBinaryWriter::write() {
if (func->prologLocation) {
parent.writeDebugLocation(*func->prologLocation);
}
writer.mapLocalsAndEmitHeader();
// Stack to track indices of catches within a try
SmallVector<Index, 4> catchIndexStack;
for (auto* inst : stackIR) {
if (!inst) {
continue; // a nullptr is just something we can skip
}
switch (inst->op) {
case StackInst::TryBegin:
catchIndexStack.push_back(0);
[[fallthrough]];
case StackInst::Basic:
case StackInst::BlockBegin:
case StackInst::IfBegin:
case StackInst::LoopBegin:
case StackInst::TryTableBegin: {
if (sourceMap) {
parent.writeDebugLocation(inst->origin, func);
}
writer.visit(inst->origin);
if (sourceMap) {
parent.writeDebugLocationEnd(inst->origin, func);
}
break;
}
case StackInst::TryEnd:
catchIndexStack.pop_back();
[[fallthrough]];
case StackInst::BlockEnd:
case StackInst::IfEnd:
case StackInst::LoopEnd:
case StackInst::TryTableEnd: {
writer.emitScopeEnd(inst->origin);
break;
}
case StackInst::IfElse: {
writer.emitIfElse(inst->origin->cast<If>());
break;
}
case StackInst::Catch: {
writer.emitCatch(inst->origin->cast<Try>(), catchIndexStack.back()++);
break;
}
case StackInst::CatchAll: {
writer.emitCatchAll(inst->origin->cast<Try>());
break;
}
case StackInst::Delegate: {
writer.emitDelegate(inst->origin->cast<Try>());
// Delegates end the try, like a TryEnd.
catchIndexStack.pop_back();
break;
}
default:
WASM_UNREACHABLE("unexpected op");
}
}
// Indicate the debug location corresponding to the end opcode that
// terminates the function code.
if (func->epilogLocation) {
parent.writeDebugLocation(*func->epilogLocation);
} else {
// The end opcode has no debug location.
parent.writeNoDebugLocation();
}
writer.emitFunctionEnd();
}
} // namespace wasm