blob: 761c17116b6c129af4b244127c873b7563ed6454 [file] [log] [blame]
/*
* 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 "wasm.h"
#include "ir/branch-utils.h"
#include "wasm-traversal.h"
namespace wasm {
// shared constants
Name WASM("wasm");
Name RETURN_FLOW("*return:)*");
Name NONCONSTANT_FLOW("*nonconstant:)*");
namespace BinaryConsts {
namespace UserSections {
const char* Name = "name";
const char* SourceMapUrl = "sourceMappingURL";
const char* Dylink = "dylink";
const char* Linking = "linking";
const char* Producers = "producers";
const char* TargetFeatures = "target_features";
const char* AtomicsFeature = "atomics";
const char* BulkMemoryFeature = "bulk-memory";
const char* ExceptionHandlingFeature = "exception-handling";
const char* MutableGlobalsFeature = "mutable-globals";
const char* TruncSatFeature = "nontrapping-fptoint";
const char* SignExtFeature = "sign-ext";
const char* SIMD128Feature = "simd128";
const char* TailCallFeature = "tail-call";
const char* ReferenceTypesFeature = "reference-types";
const char* MultivalueFeature = "multivalue";
const char* GCFeature = "gc";
const char* Memory64Feature = "memory64";
const char* TypedFunctionReferencesFeature = "typed-function-references";
} // namespace UserSections
} // namespace BinaryConsts
Name MEMORY_BASE("__memory_base");
Name TABLE_BASE("__table_base");
Name STACK_POINTER("__stack_pointer");
Name GET_TEMP_RET0("getTempRet0");
Name SET_TEMP_RET0("setTempRet0");
Name NEW_SIZE("newSize");
Name MODULE("module");
Name START("start");
Name GLOBAL("global");
Name FUNC("func");
Name PARAM("param");
Name RESULT("result");
Name MEMORY("memory");
Name DATA("data");
Name PASSIVE("passive");
Name EXPORT("export");
Name IMPORT("import");
Name TABLE("table");
Name ELEM("elem");
Name DECLARE("declare");
Name OFFSET("offset");
Name ITEM("item");
Name LOCAL("local");
Name TYPE("type");
Name REF("ref");
Name NULL_("null");
Name CALL("call");
Name CALL_INDIRECT("call_indirect");
Name BLOCK("block");
Name BR_IF("br_if");
Name THEN("then");
Name ELSE("else");
Name _NAN("NaN");
Name _INFINITY("Infinity");
Name NEG_INFINITY("-infinity");
Name NEG_NAN("-nan");
Name CASE("case");
Name BR("br");
Name FUNCREF("funcref");
Name FAKE_RETURN("fake_return_waka123");
Name DELEGATE_CALLER_TARGET("delegate_caller_target_waka123");
Name MUT("mut");
Name SPECTEST("spectest");
Name PRINT("print");
Name EXIT("exit");
Name SHARED("shared");
Name TAG("tag");
// Expressions
void Expression::dump() { std::cout << *this << '\n'; }
const char* getExpressionName(Expression* curr) {
switch (curr->_id) {
#define DELEGATE(CLASS_TO_VISIT) \
case Expression::Id::CLASS_TO_VISIT##Id: \
return #CLASS_TO_VISIT;
#include "wasm-delegations.def"
default:
WASM_UNREACHABLE("invalid id");
}
}
Literal getLiteralFromConstExpression(Expression* curr) {
// TODO: Do we need this function given that Properties::getLiteral
// (currently) does the same?
assert(Properties::isConstantExpression(curr));
return Properties::getLiteral(curr);
}
Literals getLiteralsFromConstExpression(Expression* curr) {
// TODO: Do we need this function given that Properties::getLiterals
// (currently) does the same?
if (auto* t = curr->dynCast<TupleMake>()) {
Literals values;
for (auto* operand : t->operands) {
values.push_back(getLiteralFromConstExpression(operand));
}
return values;
} else {
return {getLiteralFromConstExpression(curr)};
}
}
// a block is unreachable if one of its elements is unreachable,
// and there are no branches to it
static void
handleUnreachable(Block* block,
Block::Breakability breakability = Block::Unknown) {
if (block->type == Type::unreachable) {
return; // nothing to do
}
if (block->list.size() == 0) {
return; // nothing to do
}
// if we are concrete, stop - even an unreachable child
// won't change that (since we have a break with a value,
// or the final child flows out a value)
if (block->type.isConcrete()) {
return;
}
// look for an unreachable child
for (auto* child : block->list) {
if (child->type == Type::unreachable) {
// there is an unreachable child, so we are unreachable, unless we have a
// break
if (breakability == Block::Unknown) {
breakability = BranchUtils::BranchSeeker::has(block, block->name)
? Block::HasBreak
: Block::NoBreak;
}
if (breakability == Block::NoBreak) {
block->type = Type::unreachable;
}
return;
}
}
}
void Block::finalize() {
if (list.size() == 0) {
type = Type::none;
return;
}
// The default type is what is at the end. Next we need to see if breaks and/
// or unreachabitily change that.
type = list.back()->type;
if (!name.is()) {
// Nothing branches here, so this is easy.
handleUnreachable(this, NoBreak);
return;
}
// The default type is according to the value that flows out.
BranchUtils::BranchSeeker seeker(this->name);
Expression* temp = this;
seeker.walk(temp);
if (seeker.found) {
// Calculate the supertype of the branch types and the flowed-out type. If
// there is no supertype among the available types, assume the current type
// is already correct. TODO: calculate proper LUBs to compute a new correct
// type in this situation.
seeker.types.insert(type);
type = Type::getLeastUpperBound(seeker.types);
} else {
// There are no branches, so this block may be unreachable.
handleUnreachable(this, NoBreak);
}
}
void Block::finalize(Type type_) {
type = type_;
if (type == Type::none && list.size() > 0) {
handleUnreachable(this);
}
}
void Block::finalize(Type type_, Breakability breakability) {
type = type_;
if (type == Type::none && list.size() > 0) {
handleUnreachable(this, breakability);
}
}
void If::finalize(Type type_) {
type = type_;
if (type == Type::none && (condition->type == Type::unreachable ||
(ifFalse && ifTrue->type == Type::unreachable &&
ifFalse->type == Type::unreachable))) {
type = Type::unreachable;
}
}
void If::finalize() {
type = ifFalse ? Type::getLeastUpperBound(ifTrue->type, ifFalse->type)
: Type::none;
// if the arms return a value, leave it even if the condition
// is unreachable, we still mark ourselves as having that type, e.g.
// (if (result i32)
// (unreachable)
// (i32.const 10)
// (i32.const 20)
// )
// otherwise, if the condition is unreachable, so is the if
if (type == Type::none && condition->type == Type::unreachable) {
type = Type::unreachable;
}
}
void Loop::finalize(Type type_) {
type = type_;
if (type == Type::none && body->type == Type::unreachable) {
type = Type::unreachable;
}
}
void Loop::finalize() { type = body->type; }
void Break::finalize() {
if (condition) {
if (condition->type == Type::unreachable) {
type = Type::unreachable;
} else if (value) {
type = value->type;
} else {
type = Type::none;
}
} else {
type = Type::unreachable;
}
}
void Switch::finalize() { type = Type::unreachable; }
// Sets the type to unreachable if there is an unreachable operand. Returns true
// if so.
template<typename T> bool handleUnreachableOperands(T* curr) {
for (auto* child : curr->operands) {
if (child->type == Type::unreachable) {
curr->type = Type::unreachable;
return true;
}
}
return false;
}
void Call::finalize() {
handleUnreachableOperands(this);
if (isReturn) {
type = Type::unreachable;
}
}
void CallIndirect::finalize() {
type = sig.results;
handleUnreachableOperands(this);
if (isReturn) {
type = Type::unreachable;
}
if (target->type == Type::unreachable) {
type = Type::unreachable;
}
}
bool LocalSet::isTee() const { return type != Type::none; }
// Changes to local.tee. The type of the local should be given.
void LocalSet::makeTee(Type type_) {
type = type_;
finalize(); // type may need to be unreachable
}
// Changes to local.set.
void LocalSet::makeSet() {
type = Type::none;
finalize(); // type may need to be unreachable
}
void LocalSet::finalize() {
if (value->type == Type::unreachable) {
type = Type::unreachable;
}
}
void GlobalSet::finalize() {
if (value->type == Type::unreachable) {
type = Type::unreachable;
}
}
void Load::finalize() {
if (ptr->type == Type::unreachable) {
type = Type::unreachable;
}
}
void Store::finalize() {
assert(valueType != Type::none); // must be set
if (ptr->type == Type::unreachable || value->type == Type::unreachable) {
type = Type::unreachable;
} else {
type = Type::none;
}
}
void AtomicRMW::finalize() {
if (ptr->type == Type::unreachable || value->type == Type::unreachable) {
type = Type::unreachable;
}
}
void AtomicCmpxchg::finalize() {
if (ptr->type == Type::unreachable || expected->type == Type::unreachable ||
replacement->type == Type::unreachable) {
type = Type::unreachable;
}
}
void AtomicWait::finalize() {
type = Type::i32;
if (ptr->type == Type::unreachable || expected->type == Type::unreachable ||
timeout->type == Type::unreachable) {
type = Type::unreachable;
}
}
void AtomicNotify::finalize() {
type = Type::i32;
if (ptr->type == Type::unreachable ||
notifyCount->type == Type::unreachable) {
type = Type::unreachable;
}
}
void AtomicFence::finalize() { type = Type::none; }
void SIMDExtract::finalize() {
assert(vec);
switch (op) {
case ExtractLaneSVecI8x16:
case ExtractLaneUVecI8x16:
case ExtractLaneSVecI16x8:
case ExtractLaneUVecI16x8:
case ExtractLaneVecI32x4:
type = Type::i32;
break;
case ExtractLaneVecI64x2:
type = Type::i64;
break;
case ExtractLaneVecF32x4:
type = Type::f32;
break;
case ExtractLaneVecF64x2:
type = Type::f64;
break;
default:
WASM_UNREACHABLE("unexpected op");
}
if (vec->type == Type::unreachable) {
type = Type::unreachable;
}
}
void SIMDReplace::finalize() {
assert(vec && value);
type = Type::v128;
if (vec->type == Type::unreachable || value->type == Type::unreachable) {
type = Type::unreachable;
}
}
void SIMDShuffle::finalize() {
assert(left && right);
type = Type::v128;
if (left->type == Type::unreachable || right->type == Type::unreachable) {
type = Type::unreachable;
}
}
void SIMDTernary::finalize() {
assert(a && b && c);
type = Type::v128;
if (a->type == Type::unreachable || b->type == Type::unreachable ||
c->type == Type::unreachable) {
type = Type::unreachable;
}
}
void MemoryInit::finalize() {
assert(dest && offset && size);
type = Type::none;
if (dest->type == Type::unreachable || offset->type == Type::unreachable ||
size->type == Type::unreachable) {
type = Type::unreachable;
}
}
void DataDrop::finalize() { type = Type::none; }
void MemoryCopy::finalize() {
assert(dest && source && size);
type = Type::none;
if (dest->type == Type::unreachable || source->type == Type::unreachable ||
size->type == Type::unreachable) {
type = Type::unreachable;
}
}
void MemoryFill::finalize() {
assert(dest && value && size);
type = Type::none;
if (dest->type == Type::unreachable || value->type == Type::unreachable ||
size->type == Type::unreachable) {
type = Type::unreachable;
}
}
void SIMDShift::finalize() {
assert(vec && shift);
type = Type::v128;
if (vec->type == Type::unreachable || shift->type == Type::unreachable) {
type = Type::unreachable;
}
}
void SIMDLoad::finalize() {
assert(ptr);
type = Type::v128;
if (ptr->type == Type::unreachable) {
type = Type::unreachable;
}
}
Index SIMDLoad::getMemBytes() {
switch (op) {
case Load8SplatVec128:
return 1;
case Load16SplatVec128:
return 2;
case Load32SplatVec128:
case Load32ZeroVec128:
return 4;
case Load64SplatVec128:
case Load8x8SVec128:
case Load8x8UVec128:
case Load16x4SVec128:
case Load16x4UVec128:
case Load32x2SVec128:
case Load32x2UVec128:
case Load64ZeroVec128:
return 8;
}
WASM_UNREACHABLE("unexpected op");
}
void SIMDLoadStoreLane::finalize() {
assert(ptr && vec);
type = isLoad() ? Type::v128 : Type::none;
if (ptr->type == Type::unreachable || vec->type == Type::unreachable) {
type = Type::unreachable;
}
}
Index SIMDLoadStoreLane::getMemBytes() {
switch (op) {
case Load8LaneVec128:
case Store8LaneVec128:
return 1;
case Load16LaneVec128:
case Store16LaneVec128:
return 2;
case Load32LaneVec128:
case Store32LaneVec128:
return 4;
case Load64LaneVec128:
case Store64LaneVec128:
return 8;
}
WASM_UNREACHABLE("unexpected op");
}
bool SIMDLoadStoreLane::isStore() {
switch (op) {
case Store8LaneVec128:
case Store16LaneVec128:
case Store32LaneVec128:
case Store64LaneVec128:
return true;
case Load16LaneVec128:
case Load32LaneVec128:
case Load64LaneVec128:
case Load8LaneVec128:
return false;
}
WASM_UNREACHABLE("unexpected op");
}
Const* Const::set(Literal value_) {
value = value_;
type = value.type;
return this;
}
void Const::finalize() { type = value.type; }
bool Unary::isRelational() { return op == EqZInt32 || op == EqZInt64; }
void Unary::finalize() {
if (value->type == Type::unreachable) {
type = Type::unreachable;
return;
}
switch (op) {
case ClzInt32:
case CtzInt32:
case PopcntInt32:
case NegFloat32:
case AbsFloat32:
case CeilFloat32:
case FloorFloat32:
case TruncFloat32:
case NearestFloat32:
case SqrtFloat32:
case ClzInt64:
case CtzInt64:
case PopcntInt64:
case NegFloat64:
case AbsFloat64:
case CeilFloat64:
case FloorFloat64:
case TruncFloat64:
case NearestFloat64:
case SqrtFloat64:
type = value->type;
break;
case EqZInt32:
case EqZInt64:
type = Type::i32;
break;
case ExtendS8Int32:
case ExtendS16Int32:
type = Type::i32;
break;
case ExtendSInt32:
case ExtendUInt32:
case ExtendS8Int64:
case ExtendS16Int64:
case ExtendS32Int64:
type = Type::i64;
break;
case WrapInt64:
type = Type::i32;
break;
case PromoteFloat32:
type = Type::f64;
break;
case DemoteFloat64:
type = Type::f32;
break;
case TruncSFloat32ToInt32:
case TruncUFloat32ToInt32:
case TruncSFloat64ToInt32:
case TruncUFloat64ToInt32:
case TruncSatSFloat32ToInt32:
case TruncSatUFloat32ToInt32:
case TruncSatSFloat64ToInt32:
case TruncSatUFloat64ToInt32:
case ReinterpretFloat32:
type = Type::i32;
break;
case TruncSFloat32ToInt64:
case TruncUFloat32ToInt64:
case TruncSFloat64ToInt64:
case TruncUFloat64ToInt64:
case TruncSatSFloat32ToInt64:
case TruncSatUFloat32ToInt64:
case TruncSatSFloat64ToInt64:
case TruncSatUFloat64ToInt64:
case ReinterpretFloat64:
type = Type::i64;
break;
case ReinterpretInt32:
case ConvertSInt32ToFloat32:
case ConvertUInt32ToFloat32:
case ConvertSInt64ToFloat32:
case ConvertUInt64ToFloat32:
type = Type::f32;
break;
case ReinterpretInt64:
case ConvertSInt32ToFloat64:
case ConvertUInt32ToFloat64:
case ConvertSInt64ToFloat64:
case ConvertUInt64ToFloat64:
type = Type::f64;
break;
case SplatVecI8x16:
case SplatVecI16x8:
case SplatVecI32x4:
case SplatVecI64x2:
case SplatVecF32x4:
case SplatVecF64x2:
case NotVec128:
case AbsVecI8x16:
case AbsVecI16x8:
case AbsVecI32x4:
case AbsVecI64x2:
case PopcntVecI8x16:
case NegVecI8x16:
case NegVecI16x8:
case NegVecI32x4:
case NegVecI64x2:
case AbsVecF32x4:
case NegVecF32x4:
case SqrtVecF32x4:
case CeilVecF32x4:
case FloorVecF32x4:
case TruncVecF32x4:
case NearestVecF32x4:
case AbsVecF64x2:
case NegVecF64x2:
case SqrtVecF64x2:
case CeilVecF64x2:
case FloorVecF64x2:
case TruncVecF64x2:
case NearestVecF64x2:
case ExtAddPairwiseSVecI8x16ToI16x8:
case ExtAddPairwiseUVecI8x16ToI16x8:
case ExtAddPairwiseSVecI16x8ToI32x4:
case ExtAddPairwiseUVecI16x8ToI32x4:
case TruncSatSVecF32x4ToVecI32x4:
case TruncSatUVecF32x4ToVecI32x4:
case ConvertSVecI32x4ToVecF32x4:
case ConvertUVecI32x4ToVecF32x4:
case ExtendLowSVecI8x16ToVecI16x8:
case ExtendHighSVecI8x16ToVecI16x8:
case ExtendLowUVecI8x16ToVecI16x8:
case ExtendHighUVecI8x16ToVecI16x8:
case ExtendLowSVecI16x8ToVecI32x4:
case ExtendHighSVecI16x8ToVecI32x4:
case ExtendLowUVecI16x8ToVecI32x4:
case ExtendHighUVecI16x8ToVecI32x4:
case ExtendLowSVecI32x4ToVecI64x2:
case ExtendHighSVecI32x4ToVecI64x2:
case ExtendLowUVecI32x4ToVecI64x2:
case ExtendHighUVecI32x4ToVecI64x2:
case ConvertLowSVecI32x4ToVecF64x2:
case ConvertLowUVecI32x4ToVecF64x2:
case TruncSatZeroSVecF64x2ToVecI32x4:
case TruncSatZeroUVecF64x2ToVecI32x4:
case DemoteZeroVecF64x2ToVecF32x4:
case PromoteLowVecF32x4ToVecF64x2:
type = Type::v128;
break;
case AnyTrueVec128:
case AllTrueVecI8x16:
case AllTrueVecI16x8:
case AllTrueVecI32x4:
case AllTrueVecI64x2:
case BitmaskVecI8x16:
case BitmaskVecI16x8:
case BitmaskVecI32x4:
case BitmaskVecI64x2:
type = Type::i32;
break;
case InvalidUnary:
WASM_UNREACHABLE("invalid unary op");
}
}
bool Binary::isRelational() {
switch (op) {
case EqInt32:
case NeInt32:
case LtSInt32:
case LtUInt32:
case LeSInt32:
case LeUInt32:
case GtSInt32:
case GtUInt32:
case GeSInt32:
case GeUInt32:
case EqInt64:
case NeInt64:
case LtSInt64:
case LtUInt64:
case LeSInt64:
case LeUInt64:
case GtSInt64:
case GtUInt64:
case GeSInt64:
case GeUInt64:
case EqFloat32:
case NeFloat32:
case LtFloat32:
case LeFloat32:
case GtFloat32:
case GeFloat32:
case EqFloat64:
case NeFloat64:
case LtFloat64:
case LeFloat64:
case GtFloat64:
case GeFloat64:
return true;
default:
return false;
}
}
void Binary::finalize() {
assert(left && right);
if (left->type == Type::unreachable || right->type == Type::unreachable) {
type = Type::unreachable;
} else if (isRelational()) {
type = Type::i32;
} else {
type = left->type;
}
}
void Select::finalize(Type type_) { type = type_; }
void Select::finalize() {
assert(ifTrue && ifFalse);
if (ifTrue->type == Type::unreachable || ifFalse->type == Type::unreachable ||
condition->type == Type::unreachable) {
type = Type::unreachable;
} else {
type = Type::getLeastUpperBound(ifTrue->type, ifFalse->type);
}
}
void Drop::finalize() {
if (value->type == Type::unreachable) {
type = Type::unreachable;
} else {
type = Type::none;
}
}
void MemorySize::make64() { type = ptrType = Type::i64; }
void MemorySize::finalize() { type = ptrType; }
void MemoryGrow::make64() { type = ptrType = Type::i64; }
void MemoryGrow::finalize() {
if (delta->type == Type::unreachable) {
type = Type::unreachable;
} else {
type = ptrType;
}
}
void RefNull::finalize(HeapType heapType) { type = Type(heapType, Nullable); }
void RefNull::finalize(Type type_) { type = type_; }
void RefNull::finalize() {}
void RefIs::finalize() {
if (value->type == Type::unreachable) {
type = Type::unreachable;
return;
}
type = Type::i32;
}
void RefFunc::finalize() {
// No-op. We assume that the full proper typed function type has been applied
// previously.
}
void RefFunc::finalize(Type type_) { type = type_; }
void RefEq::finalize() {
if (left->type == Type::unreachable || right->type == Type::unreachable) {
type = Type::unreachable;
} else {
type = Type::i32;
}
}
void Try::finalize() {
// If none of the component bodies' type is a supertype of the others, assume
// the current type is already correct. TODO: Calculate a proper LUB.
std::unordered_set<Type> types{body->type};
for (auto catchBody : catchBodies) {
types.insert(catchBody->type);
}
type = Type::getLeastUpperBound(types);
}
void Try::finalize(Type type_) {
type = type_;
bool allUnreachable = body->type == Type::unreachable;
for (auto catchBody : catchBodies) {
allUnreachable &= catchBody->type == Type::unreachable;
}
if (type == Type::none && allUnreachable) {
type = Type::unreachable;
}
}
void Throw::finalize() { type = Type::unreachable; }
void Rethrow::finalize() { type = Type::unreachable; }
void TupleMake::finalize() {
std::vector<Type> types;
for (auto* op : operands) {
if (op->type == Type::unreachable) {
type = Type::unreachable;
return;
}
types.push_back(op->type);
}
type = Type(types);
}
void TupleExtract::finalize() {
if (tuple->type == Type::unreachable) {
type = Type::unreachable;
} else {
assert(index < tuple->type.size());
type = tuple->type[index];
}
}
void I31New::finalize() {
if (value->type == Type::unreachable) {
type = Type::unreachable;
} else {
type = Type::i31ref;
}
}
void I31Get::finalize() {
if (i31->type == Type::unreachable) {
type = Type::unreachable;
} else {
type = Type::i32;
}
}
void CallRef::finalize() {
handleUnreachableOperands(this);
if (isReturn) {
type = Type::unreachable;
}
if (target->type == Type::unreachable) {
type = Type::unreachable;
}
}
void CallRef::finalize(Type type_) {
type = type_;
finalize();
}
void RefTest::finalize() {
if (ref->type == Type::unreachable || rtt->type == Type::unreachable) {
type = Type::unreachable;
} else {
type = Type::i32;
}
}
void RefCast::finalize() {
if (ref->type == Type::unreachable || rtt->type == Type::unreachable) {
type = Type::unreachable;
} else {
// The output of ref.cast may be null if the input is null (in that case the
// null is passed through).
type = Type(rtt->type.getHeapType(), ref->type.getNullability());
}
}
void BrOn::finalize() {
if (ref->type == Type::unreachable ||
(rtt && rtt->type == Type::unreachable)) {
type = Type::unreachable;
return;
}
switch (op) {
case BrOnNull:
// If we do not branch, we flow out the existing value as non-null.
type = Type(ref->type.getHeapType(), NonNullable);
break;
case BrOnNonNull:
// If we do not branch, we flow out nothing (the spec could also have had
// us flow out the null, but it does not).
type = Type::none;
break;
case BrOnCast:
case BrOnFunc:
case BrOnData:
case BrOnI31:
// If we do not branch, we return the input in this case.
type = ref->type;
break;
case BrOnCastFail:
// If we do not branch, the cast worked, and we have something of the cast
// type.
type = Type(rtt->type.getHeapType(), NonNullable);
break;
case BrOnNonFunc:
type = Type(HeapType::func, NonNullable);
break;
case BrOnNonData:
type = Type(HeapType::data, NonNullable);
break;
case BrOnNonI31:
type = Type(HeapType::i31, NonNullable);
break;
default:
WASM_UNREACHABLE("invalid br_on_*");
}
}
Type BrOn::getSentType() {
switch (op) {
case BrOnNull:
// BrOnNull does not send a value on the branch.
return Type::none;
case BrOnNonNull:
// If the input is unreachable, the branch is not taken, and there is no
// valid type we can report as being sent. Report it as unreachable.
if (ref->type == Type::unreachable) {
return Type::unreachable;
}
// BrOnNonNull sends the non-nullable type on the branch.
return Type(ref->type.getHeapType(), NonNullable);
case BrOnCast:
if (ref->type == Type::unreachable) {
return Type::unreachable;
}
return Type(rtt->type.getHeapType(), NonNullable);
case BrOnFunc:
return Type::funcref;
case BrOnData:
return Type::dataref;
case BrOnI31:
return Type::i31ref;
case BrOnCastFail:
case BrOnNonFunc:
case BrOnNonData:
case BrOnNonI31:
return ref->type;
default:
WASM_UNREACHABLE("invalid br_on_*");
}
}
void RttCanon::finalize() {
// Nothing to do - the type must have been set already during construction.
}
void RttSub::finalize() {
if (parent->type == Type::unreachable) {
type = Type::unreachable;
}
// Else nothing to do - the type must have been set already during
// construction.
}
void StructNew::finalize() {
if (rtt->type == Type::unreachable) {
type = Type::unreachable;
return;
}
if (handleUnreachableOperands(this)) {
return;
}
type = Type(rtt->type.getHeapType(), NonNullable);
}
void StructGet::finalize() {
if (ref->type == Type::unreachable) {
type = Type::unreachable;
} else {
type = ref->type.getHeapType().getStruct().fields[index].type;
}
}
void StructSet::finalize() {
if (ref->type == Type::unreachable || value->type == Type::unreachable) {
type = Type::unreachable;
} else {
type = Type::none;
}
}
void ArrayNew::finalize() {
if (rtt->type == Type::unreachable || size->type == Type::unreachable ||
(init && init->type == Type::unreachable)) {
type = Type::unreachable;
return;
}
type = Type(rtt->type.getHeapType(), NonNullable);
}
void ArrayInit::finalize() {
if (rtt->type == Type::unreachable) {
type = Type::unreachable;
return;
}
for (auto* value : values) {
if (value->type == Type::unreachable) {
type = Type::unreachable;
return;
}
}
type = Type(rtt->type.getHeapType(), NonNullable);
}
void ArrayGet::finalize() {
if (ref->type == Type::unreachable || index->type == Type::unreachable) {
type = Type::unreachable;
} else {
type = ref->type.getHeapType().getArray().element.type;
}
}
void ArraySet::finalize() {
if (ref->type == Type::unreachable || index->type == Type::unreachable ||
value->type == Type::unreachable) {
type = Type::unreachable;
} else {
type = Type::none;
}
}
void ArrayLen::finalize() {
if (ref->type == Type::unreachable) {
type = Type::unreachable;
} else {
type = Type::i32;
}
}
void ArrayCopy::finalize() {
if (srcRef->type == Type::unreachable ||
srcIndex->type == Type::unreachable ||
destRef->type == Type::unreachable ||
destIndex->type == Type::unreachable ||
length->type == Type::unreachable) {
type = Type::unreachable;
} else {
type = Type::none;
}
}
void RefAs::finalize() {
if (value->type == Type::unreachable) {
type = Type::unreachable;
return;
}
switch (op) {
case RefAsNonNull:
type = Type(value->type.getHeapType(), NonNullable);
break;
case RefAsFunc:
type = Type(HeapType::func, NonNullable);
break;
case RefAsData:
type = Type::dataref;
break;
case RefAsI31:
type = Type::i31ref;
break;
default:
WASM_UNREACHABLE("invalid ref.as_*");
}
}
size_t Function::getNumParams() { return getParams().size(); }
size_t Function::getNumVars() { return vars.size(); }
size_t Function::getNumLocals() { return getParams().size() + vars.size(); }
bool Function::isParam(Index index) {
size_t size = getParams().size();
assert(index < size + vars.size());
return index < size;
}
bool Function::isVar(Index index) {
auto base = getVarIndexBase();
assert(index < base + vars.size());
return index >= base;
}
bool Function::hasLocalName(Index index) const {
return localNames.find(index) != localNames.end();
}
Name Function::getLocalName(Index index) { return localNames.at(index); }
void Function::setLocalName(Index index, Name name) {
assert(index < getNumLocals());
localNames[index] = name;
}
Name Function::getLocalNameOrDefault(Index index) {
auto nameIt = localNames.find(index);
if (nameIt != localNames.end()) {
return nameIt->second;
}
// this is an unnamed local
return Name();
}
Name Function::getLocalNameOrGeneric(Index index) {
auto nameIt = localNames.find(index);
if (nameIt != localNames.end()) {
return nameIt->second;
}
return Name::fromInt(index);
}
Index Function::getLocalIndex(Name name) {
auto iter = localIndices.find(name);
if (iter == localIndices.end()) {
Fatal() << "Function::getLocalIndex: " << name << " does not exist";
}
return iter->second;
}
Index Function::getVarIndexBase() { return getParams().size(); }
Type Function::getLocalType(Index index) {
auto numParams = getParams().size();
if (index < numParams) {
return getParams()[index];
} else if (isVar(index)) {
return vars[index - numParams];
} else {
WASM_UNREACHABLE("invalid local index");
}
}
void Function::clearNames() { localNames.clear(); }
void Function::clearDebugInfo() {
localIndices.clear();
debugLocations.clear();
prologLocation.clear();
epilogLocation.clear();
}
template<typename Map>
typename Map::mapped_type&
getModuleElement(Map& m, Name name, const std::string& funcName) {
auto iter = m.find(name);
if (iter == m.end()) {
Fatal() << "Module::" << funcName << ": " << name << " does not exist";
}
return iter->second;
}
Export* Module::getExport(Name name) {
return getModuleElement(exportsMap, name, "getExport");
}
Function* Module::getFunction(Name name) {
return getModuleElement(functionsMap, name, "getFunction");
}
Table* Module::getTable(Name name) {
return getModuleElement(tablesMap, name, "getTable");
}
ElementSegment* Module::getElementSegment(Name name) {
return getModuleElement(elementSegmentsMap, name, "getElementSegment");
}
Global* Module::getGlobal(Name name) {
return getModuleElement(globalsMap, name, "getGlobal");
}
Tag* Module::getTag(Name name) {
return getModuleElement(tagsMap, name, "getTag");
}
template<typename Map>
typename Map::mapped_type getModuleElementOrNull(Map& m, Name name) {
auto iter = m.find(name);
if (iter == m.end()) {
return nullptr;
}
return iter->second;
}
Export* Module::getExportOrNull(Name name) {
return getModuleElementOrNull(exportsMap, name);
}
Function* Module::getFunctionOrNull(Name name) {
return getModuleElementOrNull(functionsMap, name);
}
Table* Module::getTableOrNull(Name name) {
return getModuleElementOrNull(tablesMap, name);
}
ElementSegment* Module::getElementSegmentOrNull(Name name) {
return getModuleElementOrNull(elementSegmentsMap, name);
}
Global* Module::getGlobalOrNull(Name name) {
return getModuleElementOrNull(globalsMap, name);
}
Tag* Module::getTagOrNull(Name name) {
return getModuleElementOrNull(tagsMap, name);
}
// TODO(@warchant): refactor all usages to use variant with unique_ptr
template<typename Vector, typename Map, typename Elem>
Elem* addModuleElement(Vector& v, Map& m, Elem* curr, std::string funcName) {
if (!curr->name.is()) {
Fatal() << "Module::" << funcName << ": empty name";
}
if (getModuleElementOrNull(m, curr->name)) {
Fatal() << "Module::" << funcName << ": " << curr->name
<< " already exists";
}
v.push_back(std::unique_ptr<Elem>(curr));
m[curr->name] = curr;
return curr;
}
template<typename Vector, typename Map, typename Elem>
Elem* addModuleElement(Vector& v,
Map& m,
std::unique_ptr<Elem> curr,
std::string funcName) {
if (!curr->name.is()) {
Fatal() << "Module::" << funcName << ": empty name";
}
if (getModuleElementOrNull(m, curr->name)) {
Fatal() << "Module::" << funcName << ": " << curr->name
<< " already exists";
}
auto* ret = m[curr->name] = curr.get();
v.push_back(std::move(curr));
return ret;
}
Export* Module::addExport(Export* curr) {
return addModuleElement(exports, exportsMap, curr, "addExport");
}
Function* Module::addFunction(Function* curr) {
return addModuleElement(functions, functionsMap, curr, "addFunction");
}
Global* Module::addGlobal(Global* curr) {
return addModuleElement(globals, globalsMap, curr, "addGlobal");
}
Tag* Module::addTag(Tag* curr) {
return addModuleElement(tags, tagsMap, curr, "addTag");
}
Export* Module::addExport(std::unique_ptr<Export>&& curr) {
return addModuleElement(exports, exportsMap, std::move(curr), "addExport");
}
Function* Module::addFunction(std::unique_ptr<Function>&& curr) {
return addModuleElement(
functions, functionsMap, std::move(curr), "addFunction");
}
Table* Module::addTable(std::unique_ptr<Table>&& curr) {
return addModuleElement(tables, tablesMap, std::move(curr), "addTable");
}
ElementSegment*
Module::addElementSegment(std::unique_ptr<ElementSegment>&& curr) {
return addModuleElement(
elementSegments, elementSegmentsMap, std::move(curr), "addElementSegment");
}
Global* Module::addGlobal(std::unique_ptr<Global>&& curr) {
return addModuleElement(globals, globalsMap, std::move(curr), "addGlobal");
}
Tag* Module::addTag(std::unique_ptr<Tag>&& curr) {
return addModuleElement(tags, tagsMap, std::move(curr), "addTag");
}
void Module::addStart(const Name& s) { start = s; }
template<typename Vector, typename Map>
void removeModuleElement(Vector& v, Map& m, Name name) {
m.erase(name);
for (size_t i = 0; i < v.size(); i++) {
if (v[i]->name == name) {
v.erase(v.begin() + i);
break;
}
}
}
void Module::removeExport(Name name) {
removeModuleElement(exports, exportsMap, name);
}
void Module::removeFunction(Name name) {
removeModuleElement(functions, functionsMap, name);
}
void Module::removeTable(Name name) {
removeModuleElement(tables, tablesMap, name);
}
void Module::removeElementSegment(Name name) {
removeModuleElement(elementSegments, elementSegmentsMap, name);
}
void Module::removeGlobal(Name name) {
removeModuleElement(globals, globalsMap, name);
}
void Module::removeTag(Name name) { removeModuleElement(tags, tagsMap, name); }
template<typename Vector, typename Map, typename Elem>
void removeModuleElements(Vector& v,
Map& m,
std::function<bool(Elem* elem)> pred) {
for (auto it = m.begin(); it != m.end();) {
if (pred(it->second)) {
it = m.erase(it);
} else {
it++;
}
}
v.erase(
std::remove_if(v.begin(), v.end(), [&](auto& e) { return pred(e.get()); }),
v.end());
}
void Module::removeExports(std::function<bool(Export*)> pred) {
removeModuleElements(exports, exportsMap, pred);
}
void Module::removeFunctions(std::function<bool(Function*)> pred) {
removeModuleElements(functions, functionsMap, pred);
}
void Module::removeTables(std::function<bool(Table*)> pred) {
removeModuleElements(tables, tablesMap, pred);
}
void Module::removeElementSegments(std::function<bool(ElementSegment*)> pred) {
removeModuleElements(elementSegments, elementSegmentsMap, pred);
}
void Module::removeGlobals(std::function<bool(Global*)> pred) {
removeModuleElements(globals, globalsMap, pred);
}
void Module::removeTags(std::function<bool(Tag*)> pred) {
removeModuleElements(tags, tagsMap, pred);
}
void Module::updateMaps() {
functionsMap.clear();
for (auto& curr : functions) {
functionsMap[curr->name] = curr.get();
}
exportsMap.clear();
for (auto& curr : exports) {
exportsMap[curr->name] = curr.get();
}
tablesMap.clear();
for (auto& curr : tables) {
tablesMap[curr->name] = curr.get();
}
elementSegmentsMap.clear();
for (auto& curr : elementSegments) {
elementSegmentsMap[curr->name] = curr.get();
}
globalsMap.clear();
for (auto& curr : globals) {
globalsMap[curr->name] = curr.get();
}
tagsMap.clear();
for (auto& curr : tags) {
tagsMap[curr->name] = curr.get();
}
}
void Module::clearDebugInfo() { debugInfoFileNames.clear(); }
} // namespace wasm