diff options
Diffstat (limited to 'src')
43 files changed, 451 insertions, 265 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index 3f7206f4d..0255b2847 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -1973,7 +1973,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { ret->op = parseAsmBinaryOp( ast[1]->getIString(), ast[2], ast[3], ret->left, ret->right); ret->finalize(); - if (ret->op == BinaryOp::RemSInt32 && isFloatType(ret->type)) { + if (ret->op == BinaryOp::RemSInt32 && ret->type.isFloat()) { // WebAssembly does not have floating-point remainder, we have to emit a // call to a special import of ours Call* call = allocator.alloc<Call>(); @@ -2849,7 +2849,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { if (seeker.found == 0) { auto block = allocator.alloc<Block>(); block->list.push_back(child); - if (isConcreteType(child->type)) { + if (child->type.isConcrete()) { // ensure a nop at the end, so the block has guaranteed none type // and no values fall through block->list.push_back(builder.makeNop()); diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 1cdddfdc8..d33f40cdb 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -273,6 +273,42 @@ BinaryenType BinaryenTypeExnref(void) { return exnref; } BinaryenType BinaryenTypeUnreachable(void) { return unreachable; } BinaryenType BinaryenTypeAuto(void) { return uint32_t(-1); } +BinaryenType BinaryenTypeCreate(BinaryenType* types, uint32_t numTypes) { + std::vector<Type> typeVec; + typeVec.reserve(numTypes); + for (size_t i = 0; i < numTypes; ++i) { + typeVec.push_back(Type(types[i])); + } + Type result(typeVec); + + if (tracing) { + std::string array = getTemp(); + std::cout << " {\n"; + std::cout << " BinaryenType " << array << "[] = {"; + for (size_t i = 0; i < numTypes; ++i) { + std::cout << uint32_t(types[i]); + if (i < numTypes - 1) { + std::cout << ", "; + } + } + std::cout << "};\n"; + std::cout << " BinaryenTypeCreate(" << array << ", " << numTypes + << "); // " << uint32_t(result) << "\n"; + std::cout << " }\n"; + } + + return uint32_t(result); +} + +uint32_t BinaryenTypeArity(BinaryenType t) { return Type(t).size(); } + +void BinaryenTypeExpand(BinaryenType t, BinaryenType* buf) { + const std::vector<Type>& types = Type(t).expand(); + for (size_t i = 0; i < types.size(); ++i) { + buf[i] = types[i]; + } +} + WASM_DEPRECATED BinaryenType BinaryenNone(void) { return none; } WASM_DEPRECATED BinaryenType BinaryenInt32(void) { return i32; } WASM_DEPRECATED BinaryenType BinaryenInt64(void) { return i64; } diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 6f9878535..726129983 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -95,6 +95,10 @@ BINARYEN_API BinaryenType BinaryenTypeUnreachable(void); // Not a real type. Used as the last parameter to BinaryenBlock to let // the API figure out the type instead of providing one. BINARYEN_API BinaryenType BinaryenTypeAuto(void); +BINARYEN_API BinaryenType BinaryenTypeCreate(BinaryenType* valueTypes, + uint32_t numTypes); +BINARYEN_API uint32_t BinaryenTypeArity(BinaryenType t); +BINARYEN_API void BinaryenTypeExpand(BinaryenType t, BinaryenType* buf); WASM_DEPRECATED BinaryenType BinaryenNone(void); WASM_DEPRECATED BinaryenType BinaryenInt32(void); diff --git a/src/dataflow/graph.h b/src/dataflow/graph.h index 5c01714f5..a243b0fff 100644 --- a/src/dataflow/graph.h +++ b/src/dataflow/graph.h @@ -169,7 +169,7 @@ struct Graph : public UnifiedExpressionVisitor<Graph, Node*> { assert(!node->isBad()); Builder builder(*module); auto type = node->getWasmType(); - if (!isConcreteType(type)) { + if (!type.isConcrete()) { return &bad; } auto* zero = makeZero(type); @@ -397,7 +397,7 @@ struct Graph : public UnifiedExpressionVisitor<Graph, Node*> { if (!isRelevantLocal(curr->index) || isInUnreachable()) { return &bad; } - assert(isConcreteType(curr->value->type)); + assert(curr->value->type.isConcrete()); sets.push_back(curr); expressionParentMap[curr] = parent; expressionParentMap[curr->value] = curr; @@ -603,7 +603,7 @@ struct Graph : public UnifiedExpressionVisitor<Graph, Node*> { // Helpers. - bool isRelevantType(wasm::Type type) { return isIntegerType(type); } + bool isRelevantType(wasm::Type type) { return type.isInteger(); } bool isRelevantLocal(Index index) { return isRelevantType(func->getLocalType(index)); diff --git a/src/dataflow/utils.h b/src/dataflow/utils.h index 4408c6080..2d32dbeac 100644 --- a/src/dataflow/utils.h +++ b/src/dataflow/utils.h @@ -43,7 +43,7 @@ inline std::ostream& dump(Node* node, std::ostream& o, size_t indent = 0) { o << '[' << node << ' '; switch (node->type) { case Node::Type::Var: - o << "var " << printType(node->wasmType) << ' ' << node; + o << "var " << node->wasmType << ' ' << node; break; case Node::Type::Expr: { o << "expr "; diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index b27b49bd4..9ee109f5e 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -50,12 +50,12 @@ void ReFinalize::visitBlock(Block* curr) { curr->type = curr->list.back()->type; // if concrete, it doesn't matter if we have an unreachable child, and we // don't need to look at breaks - if (isConcreteType(curr->type)) { + if (curr->type.isConcrete()) { // make sure our branches make sense for us - we may have just made // ourselves concrete for a value flowing out, while branches did not send a // value. such branches could not have been actually taken before, that is, // there were in unreachable code, but we do still need to fix them up here. - if (!isConcreteType(old)) { + if (!old.isConcrete()) { auto iter = breakValues.find(curr->name); if (iter != breakValues.end()) { // there is a break to here @@ -210,7 +210,7 @@ void ReFinalize::replaceUntaken(Expression* value, Expression* condition) { // the value is unreachable, and necessary since the type of // the condition did not have an impact before (the break/switch // type was unreachable), and might not fit in. - if (isConcreteType(condition->type)) { + if (condition->type.isConcrete()) { condition = builder.makeDrop(condition); } replacement = builder.makeSequence(value, condition); diff --git a/src/ir/block-utils.h b/src/ir/block-utils.h index 120178a47..1ed9ee413 100644 --- a/src/ir/block-utils.h +++ b/src/ir/block-utils.h @@ -39,7 +39,7 @@ simplifyToContents(Block* block, T* parent, bool allowTypeChange = false) { auto* singleton = list[0]; auto sideEffects = EffectAnalyzer(parent->getPassOptions(), singleton).hasSideEffects(); - if (!sideEffects && !isConcreteType(singleton->type)) { + if (!sideEffects && !singleton->type.isConcrete()) { // no side effects, and singleton is not returning a value, so we can // throw away the block and its contents, basically return Builder(*parent->getModule()).replaceWithIdenticalType(block); @@ -50,7 +50,7 @@ simplifyToContents(Block* block, T* parent, bool allowTypeChange = false) { // inside is unreachable (if both concrete, must match, and since no name // on block, we can't be branched to, so if singleton is unreachable, so // is the block) - assert(isConcreteType(block->type) && singleton->type == unreachable); + assert(block->type.isConcrete() && singleton->type == unreachable); // we could replace with unreachable, but would need to update all // the parent's types } diff --git a/src/ir/flat.h b/src/ir/flat.h index 749d4532d..dd72e339d 100644 --- a/src/ir/flat.h +++ b/src/ir/flat.h @@ -73,10 +73,10 @@ inline void verifyFlatness(Function* func) { UnifiedExpressionVisitor<VerifyFlatness>> { void visitExpression(Expression* curr) { if (isControlFlowStructure(curr)) { - verify(!isConcreteType(curr->type), + verify(!curr->type.isConcrete(), "control flow structures must not flow values"); } else if (curr->is<LocalSet>()) { - verify(!isConcreteType(curr->type), "tees are not allowed, only sets"); + verify(!curr->type.isConcrete(), "tees are not allowed, only sets"); } else { for (auto* child : ChildIterator(curr)) { verify(child->is<Const>() || child->is<LocalGet>() || @@ -98,7 +98,7 @@ inline void verifyFlatness(Function* func) { VerifyFlatness verifier; verifier.walkFunction(func); verifier.setFunction(func); - verifier.verify(!isConcreteType(func->body->type), + verifier.verify(!func->body->type.isConcrete(), "function bodies must not flow values"); } diff --git a/src/ir/load-utils.h b/src/ir/load-utils.h index c7e9bad99..037f5297b 100644 --- a/src/ir/load-utils.h +++ b/src/ir/load-utils.h @@ -31,7 +31,7 @@ inline bool isSignRelevant(Load* load) { if (load->type == unreachable) { return false; } - return !isFloatType(type) && load->bytes < getTypeSize(type); + return !type.isFloat() && load->bytes < getTypeSize(type); } // check if a load can be signed (which some opts want to do) diff --git a/src/ir/type-updating.h b/src/ir/type-updating.h index 40c17427c..1fa891ec1 100644 --- a/src/ir/type-updating.h +++ b/src/ir/type-updating.h @@ -234,7 +234,7 @@ struct TypeUpdater // but exceptions exist if (auto* block = curr->dynCast<Block>()) { // if the block has a fallthrough, it can keep its type - if (isConcreteType(block->list.back()->type)) { + if (block->list.back()->type.isConcrete()) { return; // did not turn } // if the block has breaks, it can keep its type @@ -265,7 +265,7 @@ struct TypeUpdater // unreachable, and it does this efficiently, without scanning the full // contents void maybeUpdateTypeToUnreachable(Block* curr) { - if (!isConcreteType(curr->type)) { + if (!curr->type.isConcrete()) { return; // nothing concrete to change to unreachable } if (curr->name.is() && blockInfos[curr->name].numBreaks > 0) { @@ -279,7 +279,7 @@ struct TypeUpdater if (curr->type == unreachable) { return; // no change possible } - if (!curr->list.empty() && isConcreteType(curr->list.back()->type)) { + if (!curr->list.empty() && curr->list.back()->type.isConcrete()) { // should keep type due to fallthrough, even if has an unreachable child return; } @@ -296,7 +296,7 @@ struct TypeUpdater // can remove a concrete type and turn the if unreachable when it is // unreachable void maybeUpdateTypeToUnreachable(If* curr) { - if (!isConcreteType(curr->type)) { + if (!curr->type.isConcrete()) { return; // nothing concrete to change to unreachable } curr->finalize(); @@ -306,7 +306,7 @@ struct TypeUpdater } void maybeUpdateTypeToUnreachable(Try* curr) { - if (!isConcreteType(curr->type)) { + if (!curr->type.isConcrete()) { return; // nothing concrete to change to unreachable } curr->finalize(); diff --git a/src/ir/utils.h b/src/ir/utils.h index ca8803741..e8c5b78b3 100644 --- a/src/ir/utils.h +++ b/src/ir/utils.h @@ -251,7 +251,7 @@ struct AutoDrop : public WalkerPass<ExpressionStackWalker<AutoDrop>> { bool maybeDrop(Expression*& child) { bool acted = false; - if (isConcreteType(child->type)) { + if (child->type.isConcrete()) { expressionStack.push_back(child); if (!ExpressionAnalyzer::isResultUsed(expressionStack, getFunction()) && !ExpressionAnalyzer::isResultDropped(expressionStack)) { @@ -271,7 +271,7 @@ struct AutoDrop : public WalkerPass<ExpressionStackWalker<AutoDrop>> { } for (Index i = 0; i < curr->list.size() - 1; i++) { auto* child = curr->list[i]; - if (isConcreteType(child->type)) { + if (child->type.isConcrete()) { curr->list[i] = Builder(*getModule()).makeDrop(child); } } @@ -300,7 +300,7 @@ struct AutoDrop : public WalkerPass<ExpressionStackWalker<AutoDrop>> { void doWalkFunction(Function* curr) { ReFinalize().walkFunctionInModule(curr, getModule()); walk(curr->body); - if (curr->result == none && isConcreteType(curr->body->type)) { + if (curr->result == none && curr->body->type.isConcrete()) { curr->body = Builder(*getModule()).makeDrop(curr->body); } ReFinalize().walkFunctionInModule(curr, getModule()); diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 37d2432cb..1f2c414e5 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -42,6 +42,26 @@ Module['exnref'] = Module['_BinaryenTypeExnref'](); Module['unreachable'] = Module['_BinaryenTypeUnreachable'](); Module['auto'] = /* deprecated */ Module['undefined'] = Module['_BinaryenTypeAuto'](); +Module['createType'] = function(types) { + return preserveStack(function() { + var array = i32sToStack(types); + return Module['_BinaryenTypeCreate'](array, types.length); + }); +}; + +Module['expandType'] = function(ty) { + return preserveStack(function() { + var numTypes = Module['_BinaryenTypeArity'](ty); + var array = stackAlloc(numTypes << 2); + Module['_BinaryenTypeExpand'](ty, array); + var types = []; + for (var i = 0; i < numTypes; i++) { + types.push(HEAPU32[(array >>> 2) + i]); + } + return types; + }); +}; + // Expression ids Module['InvalidId'] = Module['_BinaryenInvalidId'](); Module['BlockId'] = Module['_BinaryenBlockId'](); diff --git a/src/parsing.h b/src/parsing.h index 49051800f..2591c4357 100644 --- a/src/parsing.h +++ b/src/parsing.h @@ -82,7 +82,7 @@ parseConst(cashew::IString s, Type type, MixedArena& allocator) { const char* str = s.str; auto ret = allocator.alloc<Const>(); ret->type = type; - if (isFloatType(type)) { + if (type.isFloat()) { if (s == _INFINITY) { switch (type) { case f32: diff --git a/src/passes/Asyncify.cpp b/src/passes/Asyncify.cpp index dd8845577..818d61907 100644 --- a/src/passes/Asyncify.cpp +++ b/src/passes/Asyncify.cpp @@ -948,7 +948,7 @@ private: builder->makeLocalGet(oldState, i32)), builder->makeUnreachable()); Expression* rep; - if (isConcreteType(call->type)) { + if (call->type.isConcrete()) { auto temp = builder->addVar(func, call->type); rep = builder->makeBlock({ builder->makeLocalSet(temp, call), diff --git a/src/passes/CodeFolding.cpp b/src/passes/CodeFolding.cpp index d60bc76d1..818bb33b8 100644 --- a/src/passes/CodeFolding.cpp +++ b/src/passes/CodeFolding.cpp @@ -139,7 +139,7 @@ struct CodeFolding : public WalkerPass<ControlFlowWalker<CodeFolding>> { // elements out of it if there is a value being returned) Block* parent = controlFlowStack.back()->dynCast<Block>(); if (parent && curr == parent->list.back() && - !isConcreteType(parent->list.back()->type)) { + !parent->list.back()->type.isConcrete()) { breakTails[curr->name].push_back(Tail(curr, parent)); } else { unoptimizables.insert(curr->name); @@ -189,7 +189,7 @@ struct CodeFolding : public WalkerPass<ControlFlowWalker<CodeFolding>> { return; } // we can't optimize a fallthrough value - if (isConcreteType(curr->list.back()->type)) { + if (curr->list.back()->type.isConcrete()) { return; } auto iter = breakTails.find(curr->name); diff --git a/src/passes/DataFlowOpts.cpp b/src/passes/DataFlowOpts.cpp index 62d2e5f24..dc8f6ca94 100644 --- a/src/passes/DataFlowOpts.cpp +++ b/src/passes/DataFlowOpts.cpp @@ -103,7 +103,7 @@ struct DataFlowOpts : public WalkerPass<PostWalker<DataFlowOpts>> { assert(!node->isConst()); // If this is a concrete value (not e.g. an eqz of unreachable), // it can definitely be precomputed into a constant. - if (isConcreteType(node->expr->type)) { + if (node->expr->type.isConcrete()) { // This can be precomputed. // TODO not just all-constant inputs? E.g. i32.mul of 0 and X. optimizeExprToConstant(node); diff --git a/src/passes/DeadArgumentElimination.cpp b/src/passes/DeadArgumentElimination.cpp index 9ef762f98..8e1da3ac5 100644 --- a/src/passes/DeadArgumentElimination.cpp +++ b/src/passes/DeadArgumentElimination.cpp @@ -458,7 +458,7 @@ private: } } returnUpdater(func, module); // Remove any value flowing out. - if (isConcreteType(func->body->type)) { + if (func->body->type.isConcrete()) { func->body = builder.makeDrop(func->body); } // Remove the drops on the calls. diff --git a/src/passes/Flatten.cpp b/src/passes/Flatten.cpp index 9caf6cae8..ee7a93b72 100644 --- a/src/passes/Flatten.cpp +++ b/src/passes/Flatten.cpp @@ -84,7 +84,7 @@ struct Flatten block->list.swap(newList); // remove a block return value auto type = block->type; - if (isConcreteType(type)) { + if (type.isConcrete()) { // if there is a temp index for breaking to the block, use that Index temp; auto iter = breakTemps.find(block->name); @@ -94,7 +94,7 @@ struct Flatten temp = builder.addVar(getFunction(), type); } auto*& last = block->list.back(); - if (isConcreteType(last->type)) { + if (last->type.isConcrete()) { last = builder.makeLocalSet(temp, last); } block->finalize(none); @@ -114,12 +114,12 @@ struct Flatten auto* originalIfFalse = iff->ifFalse; auto type = iff->type; Expression* prelude = nullptr; - if (isConcreteType(type)) { + if (type.isConcrete()) { Index temp = builder.addVar(getFunction(), type); - if (isConcreteType(iff->ifTrue->type)) { + if (iff->ifTrue->type.isConcrete()) { iff->ifTrue = builder.makeLocalSet(temp, iff->ifTrue); } - if (iff->ifFalse && isConcreteType(iff->ifFalse->type)) { + if (iff->ifFalse && iff->ifFalse->type.isConcrete()) { iff->ifFalse = builder.makeLocalSet(temp, iff->ifFalse); } // the whole if (+any preludes from the condition) is now a prelude @@ -143,7 +143,7 @@ struct Flatten Expression* rep = loop; auto* originalBody = loop->body; auto type = loop->type; - if (isConcreteType(type)) { + if (type.isConcrete()) { Index temp = builder.addVar(getFunction(), type); loop->body = builder.makeLocalSet(temp, loop->body); // and we leave just a get of the value @@ -180,14 +180,14 @@ struct Flatten } else if (auto* br = curr->dynCast<Break>()) { if (br->value) { auto type = br->value->type; - if (isConcreteType(type)) { + if (type.isConcrete()) { // we are sending a value. use a local instead Index temp = getTempForBreakTarget(br->name, type); ourPreludes.push_back(builder.makeLocalSet(temp, br->value)); if (br->condition) { // the value must also flow out ourPreludes.push_back(br); - if (isConcreteType(br->type)) { + if (br->type.isConcrete()) { replaceCurrent(builder.makeLocalGet(temp, type)); } else { assert(br->type == unreachable); @@ -205,7 +205,7 @@ struct Flatten } else if (auto* sw = curr->dynCast<Switch>()) { if (sw->value) { auto type = sw->value->type; - if (isConcreteType(type)) { + if (type.isConcrete()) { // we are sending a value. use a local instead Index temp = builder.addVar(getFunction(), type); ourPreludes.push_back(builder.makeLocalSet(temp, sw->value)); @@ -266,7 +266,7 @@ struct Flatten void visitFunction(Function* curr) { auto* originalBody = curr->body; // if the body is a block with a result, turn that into a return - if (isConcreteType(curr->body->type)) { + if (curr->body->type.isConcrete()) { curr->body = Builder(*getModule()).makeReturn(curr->body); } // the body may have preludes diff --git a/src/passes/I64ToI32Lowering.cpp b/src/passes/I64ToI32Lowering.cpp index 5d7d5998c..b51858474 100644 --- a/src/passes/I64ToI32Lowering.cpp +++ b/src/passes/I64ToI32Lowering.cpp @@ -1504,7 +1504,7 @@ private: std::vector<Expression*> children; bool hasUnreachable = false; for (auto* child : ChildIterator(curr)) { - if (isConcreteType(child->type)) { + if (child->type.isConcrete()) { child = builder->makeDrop(child); } else if (child->type == unreachable) { hasUnreachable = true; diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index 53a57cfeb..10b41abc0 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -190,7 +190,7 @@ struct Updater : public PostWalker<Updater> { template<typename T> void handleReturnCall(T* curr, Type targetType) { curr->isReturn = false; curr->type = targetType; - if (isConcreteType(targetType)) { + if (targetType.isConcrete()) { replaceCurrent(builder->makeBreak(returnName, curr)); } else { replaceCurrent(builder->blockify(curr, builder->makeBreak(returnName))); @@ -226,7 +226,7 @@ doInlining(Module* module, Function* into, const InliningAction& action) { auto* block = builder.makeBlock(); block->name = Name(std::string("__inlined_func$") + from->name.str); if (call->isReturn) { - if (isConcreteType(retType)) { + if (retType.isConcrete()) { *action.callSite = builder.makeReturn(block); } else { *action.callSite = builder.makeSequence(block, builder.makeReturn()); diff --git a/src/passes/LocalCSE.cpp b/src/passes/LocalCSE.cpp index f3e03d95d..afd30c040 100644 --- a/src/passes/LocalCSE.cpp +++ b/src/passes/LocalCSE.cpp @@ -208,7 +208,7 @@ struct LocalCSE : public WalkerPass<LinearExecutionWalker<LocalCSE>> { if (value->is<LocalGet>()) { return false; // trivial, this is what we optimize to! } - if (!isConcreteType(value->type)) { + if (!value->type.isConcrete()) { return false; // don't bother with unreachable etc. } if (EffectAnalyzer(getPassOptions(), value).hasSideEffects()) { diff --git a/src/passes/MergeBlocks.cpp b/src/passes/MergeBlocks.cpp index bba64c381..babbf77ba 100644 --- a/src/passes/MergeBlocks.cpp +++ b/src/passes/MergeBlocks.cpp @@ -164,7 +164,7 @@ struct BreakValueDropper : public ControlFlowWalker<BreakValueDropper> { // (block (drop value) (br_if)) with type none, which does not need a drop // likewise, unreachable does not need to be dropped, so we just leave drops // of concrete values - if (!isConcreteType(curr->value->type)) { + if (!curr->value->type.isConcrete()) { replaceCurrent(curr->value); } } @@ -240,7 +240,7 @@ optimizeBlock(Block* curr, Module* module, PassOptions& passOptions) { // we can do it! // reuse the drop, if we still need it auto* last = childBlock->list.back(); - if (isConcreteType(last->type)) { + if (last->type.isConcrete()) { drop->value = last; drop->finalize(); childBlock->list.back() = drop; @@ -281,7 +281,7 @@ optimizeBlock(Block* curr, Module* module, PassOptions& passOptions) { if (childBlock->name.is()) { // If it has a concrete value, then breaks may be sending it a value, // and we'd need to handle that. TODO - if (isConcreteType(childBlock->type)) { + if (childBlock->type.isConcrete()) { continue; } auto childName = childBlock->name; @@ -311,7 +311,7 @@ optimizeBlock(Block* curr, Module* module, PassOptions& passOptions) { // value, we would need special handling for that - not worth it, // probably TODO // FIXME is this not handled by the drop later down? - if (keepEnd < childSize && isConcreteType(childList.back()->type)) { + if (keepEnd < childSize && childList.back()->type.isConcrete()) { continue; } } @@ -362,7 +362,7 @@ optimizeBlock(Block* curr, Module* module, PassOptions& passOptions) { if (!merged.empty()) { auto* last = merged.back(); for (auto*& item : merged) { - if (item != last && isConcreteType(item->type)) { + if (item != last && item->type.isConcrete()) { Builder builder(*module); item = builder.makeDrop(item); } diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 729a42ada..64c49fc96 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -762,7 +762,7 @@ struct OptimizeInstructions } else { // the types diff. as the condition is reachable, that means the // if must be concrete while the arm is not - assert(isConcreteType(iff->type) && + assert(iff->type.isConcrete() && iff->ifTrue->type == unreachable); // emit a block with a forced type auto* ret = builder.makeBlock(); @@ -1298,7 +1298,7 @@ private: Expression* optimizeWithConstantOnRight(Binary* binary) { auto type = binary->right->type; auto* right = binary->right->cast<Const>(); - if (isIntegerType(type)) { + if (type.isInteger()) { // operations on zero if (right->value == Literal::makeFromInt32(0, type)) { if (binary->op == Abstract::getBinary(type, Abstract::Shl) || @@ -1351,7 +1351,7 @@ private: } } } - if (isIntegerType(type) || isFloatType(type)) { + if (type.isInteger() || type.isFloat()) { // note that this is correct even on floats with a NaN on the left, // as a NaN would skip the computation and just return the NaN, // and that is precisely what we do here. but, the same with -1 @@ -1375,7 +1375,7 @@ private: Expression* optimizeWithConstantOnLeft(Binary* binary) { auto type = binary->left->type; auto* left = binary->left->cast<Const>(); - if (isIntegerType(type)) { + if (type.isInteger()) { // operations on zero if (left->value == Literal::makeFromInt32(0, type)) { if ((binary->op == Abstract::getBinary(type, Abstract::Shl) || @@ -1397,7 +1397,7 @@ private: // x + 5 == 7 // => // x == 2 - if (isIntegerType(binary->left->type)) { + if (binary->left->type.isInteger()) { if (binary->op == Abstract::getBinary(type, Abstract::Eq) || binary->op == Abstract::getBinary(type, Abstract::Ne)) { if (auto* left = binary->left->dynCast<Binary>()) { diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 43ecadf7f..57a3ab27f 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -100,7 +100,7 @@ public: // If we don't need to replace the whole expression, see if there // is a value flowing through a tee. if (!replaceExpression) { - if (isConcreteType(curr->type)) { + if (curr->type.isConcrete()) { assert(curr->isTee()); return visit(curr->value); } @@ -183,12 +183,12 @@ struct Precompute // Until engines implement v128.const and we have SIMD-aware optimizations // that can break large v128.const instructions into smaller consts and // splats, do not try to precompute v128 expressions. - if (isVectorType(curr->type)) { + if (curr->type.isVector()) { return; } // try to evaluate this into a const Flow flow = precomputeExpression(curr); - if (isVectorType(flow.value.type)) { + if (flow.value.type.isVector()) { return; } if (flow.breaking()) { @@ -248,7 +248,7 @@ struct Precompute return; } // this was precomputed - if (isConcreteType(flow.value.type)) { + if (flow.value.type.isConcrete()) { replaceCurrent(Builder(*getModule()).makeConst(flow.value)); worked = true; } else { diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index bfd12dc94..f51bccf27 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -57,9 +57,7 @@ static Name printableLocal(Index index, Function* func) { // Printing "unreachable" as a instruction prefix type is not valid in wasm text // format. Print something else to make it pass. -static Type forceConcrete(Type type) { - return isConcreteType(type) ? type : i32; -} +static Type forceConcrete(Type type) { return type.isConcrete() ? type : i32; } // Prints the internal contents of an expression: everything but // the children. @@ -77,14 +75,14 @@ struct PrintExpressionContents o << ' '; printName(curr->name, o); } - if (isConcreteType(curr->type)) { - o << " (result " << printType(curr->type) << ')'; + if (curr->type.isConcrete()) { + o << ' ' << ResultType(curr->type); } } void visitIf(If* curr) { printMedium(o, "if"); - if (isConcreteType(curr->type)) { - o << " (result " << printType(curr->type) << ')'; + if (curr->type.isConcrete()) { + o << ' ' << ResultType(curr->type); } } void visitLoop(Loop* curr) { @@ -92,8 +90,8 @@ struct PrintExpressionContents if (curr->name.is()) { o << ' ' << curr->name; } - if (isConcreteType(curr->type)) { - o << " (result " << printType(curr->type) << ')'; + if (curr->type.isConcrete()) { + o << ' ' << ResultType(curr->type); } } void visitBreak(Break* curr) { @@ -147,7 +145,7 @@ struct PrintExpressionContents printName(curr->name, o); } void visitLoad(Load* curr) { - prepareColor(o) << printType(forceConcrete(curr->type)); + prepareColor(o) << forceConcrete(curr->type); if (curr->isAtomic) { o << ".atomic"; } @@ -173,7 +171,7 @@ struct PrintExpressionContents } } void visitStore(Store* curr) { - prepareColor(o) << printType(forceConcrete(curr->valueType)); + prepareColor(o) << forceConcrete(curr->valueType); if (curr->isAtomic) { o << ".atomic"; } @@ -198,7 +196,7 @@ struct PrintExpressionContents } } static void printRMWSize(std::ostream& o, Type type, uint8_t bytes) { - prepareColor(o) << printType(forceConcrete(type)) << ".atomic.rmw"; + prepareColor(o) << forceConcrete(type) << ".atomic.rmw"; if (type != unreachable && bytes != getTypeSize(type)) { if (bytes == 1) { o << '8'; @@ -257,7 +255,7 @@ struct PrintExpressionContents } void visitAtomicWait(AtomicWait* curr) { prepareColor(o); - o << printType(forceConcrete(curr->expectedType)) << ".atomic.wait"; + o << forceConcrete(curr->expectedType) << ".atomic.wait"; if (curr->offset) { o << " offset=" << curr->offset; } @@ -1306,8 +1304,8 @@ struct PrintExpressionContents } void visitTry(Try* curr) { printMedium(o, "try"); - if (isConcreteType(curr->type)) { - o << " (result " << printType(curr->type) << ')'; + if (curr->type.isConcrete()) { + o << ' ' << ResultType(curr->type); } } void visitThrow(Throw* curr) { @@ -1325,7 +1323,7 @@ struct PrintExpressionContents void visitUnreachable(Unreachable* curr) { printMinor(o, "unreachable"); } void visitPush(Push* curr) { prepareColor(o) << "push"; } void visitPop(Pop* curr) { - prepareColor(o) << printType(curr->type); + prepareColor(o) << curr->type; o << ".pop"; restoreNormalColor(o); } @@ -1413,7 +1411,7 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> { void printFullLine(Expression* expression) { !minify && doIndent(o, indent); if (full) { - o << "[" << printType(expression->type) << "] "; + o << "[" << expression->type << "] "; } visit(expression); o << maybeNewLine; @@ -1444,7 +1442,7 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> { } stack.push_back(curr); if (full) { - o << "[" << printType(curr->type) << "] "; + o << "[" << curr->type << "] "; } o << '('; PrintExpressionContents(currFunction, o).visit(curr); @@ -1872,17 +1870,11 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> { } if (curr->params.size() > 0) { o << maybeSpace; - o << '('; - printMinor(o, "param"); - for (auto& param : curr->params) { - o << ' ' << printType(param); - } - o << ')'; + o << ParamType(Type(curr->params)); } if (curr->result != none) { o << maybeSpace; - o << '('; - printMinor(o, "result ") << printType(curr->result) << ')'; + o << ResultType(curr->result); } o << ")"; } @@ -1926,9 +1918,9 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> { } void emitGlobalType(Global* curr) { if (curr->mutable_) { - o << "(mut " << printType(curr->type) << ')'; + o << "(mut " << curr->type << ')'; } else { - o << printType(curr->type); + o << curr->type; } } void visitImportedGlobal(Global* curr) { @@ -2002,20 +1994,19 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> { o << maybeSpace; o << '('; printMinor(o, "param ") << printableLocal(i, currFunction) << ' ' - << printType(curr->getLocalType(i)) << ')'; + << curr->getLocalType(i) << ')'; } } if (curr->result != none) { o << maybeSpace; - o << '('; - printMinor(o, "result ") << printType(curr->result) << ')'; + o << ResultType(curr->result); } incIndent(); for (size_t i = curr->getVarIndexBase(); i < curr->getNumLocals(); i++) { doIndent(o, indent); o << '('; printMinor(o, "local ") << printableLocal(i, currFunction) << ' ' - << printType(curr->getLocalType(i)) << ')'; + << curr->getLocalType(i) << ')'; o << maybeNewLine; } // Print the body. @@ -2064,12 +2055,9 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> { emitImportHeader(curr); o << "(event "; printName(curr->name, o); - o << maybeSpace << "(attr " << curr->attribute << ')' << maybeSpace << '('; - printMinor(o, "param"); - for (auto& param : curr->params) { - o << ' ' << printType(param); - } - o << ")))"; + o << maybeSpace << "(attr " << curr->attribute << ')' << maybeSpace; + o << ParamType(Type(curr->params)); + o << "))"; o << maybeNewLine; } void visitDefinedEvent(Event* curr) { @@ -2077,12 +2065,9 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> { o << '('; printMedium(o, "event "); printName(curr->name, o); - o << maybeSpace << "(attr " << curr->attribute << ')' << maybeSpace << '('; - printMinor(o, "param"); - for (auto& param : curr->params) { - o << ' ' << printType(param); - } - o << "))" << maybeNewLine; + o << maybeSpace << "(attr " << curr->attribute << ')' << maybeSpace; + o << ParamType(Type(curr->params)); + o << ")" << maybeNewLine; } void printTableHeader(Table* curr) { o << '('; @@ -2372,7 +2357,7 @@ std::ostream& WasmPrinter::printExpression(Expression* expression, print.setMinify(minify); if (full || isFullForced()) { print.setFull(true); - o << "[" << printType(expression->type) << "] "; + o << "[" << expression->type << "] "; } print.visit(expression); return o; @@ -2394,7 +2379,7 @@ WasmPrinter::printStackInst(StackInst* inst, std::ostream& o, Function* func) { case StackInst::BlockEnd: case StackInst::IfEnd: case StackInst::LoopEnd: { - o << "end (" << printType(inst->type) << ')'; + o << "end (" << inst->type << ')'; break; } case StackInst::IfElse: { diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 7f3a9965d..d8668196d 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -435,7 +435,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { // append to the other, if there is no returned value to concern us // can't be, since in the middle of a block - assert(!isConcreteType(iff->type)); + assert(!iff->type.isConcrete()); // ensures the first node is a block, if it isn't already, and merges // in the second, either as a single element or, if a block, by @@ -454,7 +454,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { if (!block || block->name.is()) { block = builder.makeBlock(any); } else { - assert(!isConcreteType(block->type)); + assert(!block->type.isConcrete()); } auto* other = append->dynCast<Block>(); if (!other) { @@ -910,8 +910,8 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { // Convert an if into a select, if possible and beneficial to do so. Select* selectify(If* iff) { - if (!iff->ifFalse || !isConcreteType(iff->ifTrue->type) || - !isConcreteType(iff->ifFalse->type)) { + if (!iff->ifFalse || !iff->ifTrue->type.isConcrete() || + !iff->ifFalse->type.isConcrete()) { return nullptr; } // This is always helpful for code size, but can be a tradeoff with @@ -972,8 +972,8 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { bool optimizeSetIfWithBrArm(Expression** currp) { auto* set = (*currp)->cast<LocalSet>(); auto* iff = set->value->dynCast<If>(); - if (!iff || !isConcreteType(iff->type) || - !isConcreteType(iff->condition->type)) { + if (!iff || !iff->type.isConcrete() || + !iff->condition->type.isConcrete()) { return false; } auto tryToOptimize = @@ -1047,8 +1047,8 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { bool optimizeSetIfWithCopyArm(Expression** currp) { auto* set = (*currp)->cast<LocalSet>(); auto* iff = set->value->dynCast<If>(); - if (!iff || !isConcreteType(iff->type) || - !isConcreteType(iff->condition->type)) { + if (!iff || !iff->type.isConcrete() || + !iff->condition->type.isConcrete()) { return false; } Builder builder(*getModule()); diff --git a/src/passes/SafeHeap.cpp b/src/passes/SafeHeap.cpp index 169c129b4..f29a1cf66 100644 --- a/src/passes/SafeHeap.cpp +++ b/src/passes/SafeHeap.cpp @@ -41,7 +41,7 @@ static const Name ALIGNFAULT_IMPORT("alignfault"); static Name getLoadName(Load* curr) { std::string ret = "SAFE_HEAP_LOAD_"; - ret += printType(curr->type); + ret += curr->type.toString(); ret += "_" + std::to_string(curr->bytes) + "_"; if (LoadUtils::isSignRelevant(curr) && !curr->signed_) { ret += "U_"; @@ -56,7 +56,7 @@ static Name getLoadName(Load* curr) { static Name getStoreName(Store* curr) { std::string ret = "SAFE_HEAP_STORE_"; - ret += printType(curr->valueType); + ret += curr->valueType.toString(); ret += "_" + std::to_string(curr->bytes) + "_"; if (curr->isAtomic) { ret += "A"; @@ -170,7 +170,7 @@ struct SafeHeap : public Pass { bool isPossibleAtomicOperation(Index align, Index bytes, bool shared, Type type) { - return align == bytes && shared && isIntegerType(type); + return align == bytes && shared && type.isInteger(); } void addGlobals(Module* module, FeatureSet features) { @@ -189,7 +189,7 @@ struct SafeHeap : public Pass { } for (auto signed_ : {true, false}) { load.signed_ = signed_; - if (isFloatType(type) && signed_) { + if (type.isFloat() && signed_) { continue; } for (Index align : {1, 2, 4, 8, 16}) { diff --git a/src/passes/Souperify.cpp b/src/passes/Souperify.cpp index 199db727e..c5dca51fa 100644 --- a/src/passes/Souperify.cpp +++ b/src/passes/Souperify.cpp @@ -254,7 +254,7 @@ struct Trace { (node != toInfer && excludeAsChildren.find(node) != excludeAsChildren.end())) { auto type = node->getWasmType(); - assert(isConcreteType(type)); + assert(type.isConcrete()); auto* var = Node::makeVar(type); replacements[node] = std::unique_ptr<Node>(var); node = var; @@ -458,8 +458,7 @@ struct Printer { assert(node); switch (node->type) { case Node::Type::Var: { - std::cout << "%" << indexing[node] << ":" << printType(node->wasmType) - << " = var"; + std::cout << "%" << indexing[node] << ":" << node->wasmType << " = var"; break; // nothing more to add } case Node::Type::Expr: { @@ -496,8 +495,7 @@ struct Printer { } case Node::Type::Zext: { auto* child = node->getValue(0); - std::cout << "%" << indexing[node] << ':' - << printType(child->getWasmType()); + std::cout << "%" << indexing[node] << ':' << child->getWasmType(); std::cout << " = zext "; printInternal(child); break; @@ -523,7 +521,7 @@ struct Printer { } void print(Literal value) { - std::cout << value.getInteger() << ':' << printType(value.type); + std::cout << value.getInteger() << ':' << value.type; } void printInternal(Node* node) { diff --git a/src/passes/StackIR.cpp b/src/passes/StackIR.cpp index 52ea061a3..2434c44f6 100644 --- a/src/passes/StackIR.cpp +++ b/src/passes/StackIR.cpp @@ -169,7 +169,7 @@ private: values.clear(); } // This is something we should handle, look into it. - if (isConcreteType(inst->type)) { + if (inst->type.isConcrete()) { bool optimized = false; if (auto* get = inst->origin->dynCast<LocalGet>()) { // This is a potential optimization opportunity! See if we diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index 82d904701..d97ce461b 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -216,11 +216,11 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> { auto* child = list[z]; // The last element may be used. bool used = - z == size - 1 && isConcreteType(curr->type) && + z == size - 1 && curr->type.isConcrete() && ExpressionAnalyzer::isResultUsed(expressionStack, getFunction()); auto* optimized = optimize(child, used, true); if (!optimized) { - if (isConcreteType(child->type)) { + if (child->type.isConcrete()) { // We can't just skip a final concrete element, even if it isn't used. // Instead, replace it with something that's easy to optimize out (for // example, code-folding can merge out identical zeros at the end of @@ -357,7 +357,7 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> { // note that the last element may be concrete but not the block, if the // block has an unreachable element in the middle, making the block // unreachable despite later elements and in particular the last - if (isConcreteType(last->type) && block->type == last->type) { + if (last->type.isConcrete() && block->type == last->type) { last = optimize(last, false, false); if (!last) { // we may be able to remove this, if there are no brs @@ -393,16 +393,15 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> { // unreachable, as it if is a branch, this can make that branch optimizable // and more vaccuming possible auto* iff = curr->value->dynCast<If>(); - if (iff && iff->ifFalse && isConcreteType(iff->type)) { + if (iff && iff->ifFalse && iff->type.isConcrete()) { // reuse the drop in both cases - if (iff->ifTrue->type == unreachable && - isConcreteType(iff->ifFalse->type)) { + if (iff->ifTrue->type == unreachable && iff->ifFalse->type.isConcrete()) { curr->value = iff->ifFalse; iff->ifFalse = curr; iff->type = none; replaceCurrent(iff); } else if (iff->ifFalse->type == unreachable && - isConcreteType(iff->ifTrue->type)) { + iff->ifTrue->type.isConcrete()) { curr->value = iff->ifTrue; iff->ifTrue = curr; iff->type = none; diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 13771a69d..9ef3d2e1e 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -71,7 +71,7 @@ struct ExecutionResults { // this has a result results[exp->name] = run(func, wasm, instance); // ignore the result if we hit an unreachable and returned no value - if (isConcreteType(results[exp->name].type)) { + if (results[exp->name].type.isConcrete()) { std::cout << "[fuzz-exec] note result: " << exp->name << " => " << results[exp->name] << '\n'; } diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 0e5184826..fe4a3955a 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -457,7 +457,7 @@ private: void addImportLoggingSupport() { for (auto type : getConcreteTypes()) { auto* func = new Function; - Name name = std::string("log-") + printType(type); + Name name = std::string("log-") + type.toString(); func->name = name; func->module = "fuzzing-support"; func->base = name; @@ -790,7 +790,7 @@ private: args.push_back(makeConst(type)); } Expression* invoke = builder.makeCall(func->name, args, func->result); - if (isConcreteType(func->result)) { + if (func->result.isConcrete()) { invoke = builder.makeDrop(invoke); } invocations.push_back(invoke); @@ -827,7 +827,7 @@ private: // when we should stop, emit something small (but not necessarily trivial) if (finishedInput || nesting >= 5 * NESTING_LIMIT || // hard limit (nesting >= NESTING_LIMIT && !oneIn(3))) { - if (isConcreteType(type)) { + if (type.isConcrete()) { if (oneIn(2)) { return makeConst(type); } else { @@ -996,7 +996,7 @@ private: // make something with no chance of infinite recursion Expression* makeTrivial(Type type) { - if (isConcreteType(type)) { + if (type.isConcrete()) { if (oneIn(2)) { return makeLocalGet(type); } else { @@ -1007,7 +1007,7 @@ private: } assert(type == unreachable); Expression* ret = nullptr; - if (isConcreteType(func->result)) { + if (func->result.isConcrete()) { ret = makeTrivial(func->result); } return builder.makeReturn(ret); @@ -1039,13 +1039,13 @@ private: } // give a chance to make the final element an unreachable break, instead // of concrete - a common pattern (branch to the top of a loop etc.) - if (!finishedInput && isConcreteType(type) && oneIn(2)) { + if (!finishedInput && type.isConcrete() && oneIn(2)) { ret->list.push_back(makeBreak(unreachable)); } else { ret->list.push_back(make(type)); } breakableStack.pop_back(); - if (isConcreteType(type)) { + if (type.isConcrete()) { ret->finalize(type); } else { ret->finalize(); @@ -1131,7 +1131,7 @@ private: auto* target = pick(breakableStack); auto name = getTargetName(target); auto valueType = getTargetType(target); - if (isConcreteType(type)) { + if (type.isConcrete()) { // we are flowing out a value if (valueType != type) { // we need to break to a proper place @@ -1485,7 +1485,7 @@ private: Expression* makeStore(Type type) { // exnref type cannot be stored in memory - if (!allowMemory || isReferenceType(type)) { + if (!allowMemory || type.isRef()) { return makeTrivial(type); } auto* ret = makeNonAtomicStore(type); @@ -1982,7 +1982,7 @@ private: return makeTrivial(type); } // There's no binary ops for exnref - if (isReferenceType(type)) { + if (type.isRef()) { makeTrivial(type); } @@ -2238,7 +2238,7 @@ private: auto default_ = names.back(); names.pop_back(); auto temp1 = make(i32), - temp2 = isConcreteType(valueType) ? make(valueType) : nullptr; + temp2 = valueType.isConcrete() ? make(valueType) : nullptr; return builder.makeSwitch(names, default_, temp1, temp2); } @@ -2248,8 +2248,8 @@ private: } Expression* makeReturn(Type type) { - return builder.makeReturn(isConcreteType(func->result) ? make(func->result) - : nullptr); + return builder.makeReturn(func->result.isConcrete() ? make(func->result) + : nullptr); } Expression* makeNop(Type type) { @@ -2607,7 +2607,7 @@ private: Expression* makeLogging() { auto type = getConcreteType(); return builder.makeCall( - std::string("log-") + printType(type), {make(type)}, none); + std::string("log-") + type.toString(), {make(type)}, none); } Expression* makeMemoryHashLogging() { diff --git a/src/tools/js-wrapper.h b/src/tools/js-wrapper.h index 32b46affa..34a823e97 100644 --- a/src/tools/js-wrapper.h +++ b/src/tools/js-wrapper.h @@ -113,7 +113,7 @@ static std::string generateJSWrapper(Module& wasm) { } ret += ")"; if (func->result != none) { - ret += ", '" + std::string(printType(func->result)) + "'))"; + ret += ", '" + func->result.toString() + "'))"; // TODO: getTempRet } ret += ";\n"; diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index ba9f10264..f1fbebc24 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -466,7 +466,7 @@ struct Reducer if (tryToReduceCurrentToNop()) { return; } - } else if (isConcreteType(curr->type)) { + } else if (curr->type.isConcrete()) { if (tryToReduceCurrentToConst()) { return; } @@ -556,7 +556,7 @@ struct Reducer } // Finally, try to replace with a child. for (auto* child : ChildIterator(curr)) { - if (isConcreteType(child->type) && curr->type == none) { + if (child->type.isConcrete() && curr->type == none) { if (tryToReplaceCurrent(builder->makeDrop(child))) { return; } @@ -567,13 +567,13 @@ struct Reducer } } // If that didn't work, try to replace with a child + a unary conversion - if (isConcreteType(curr->type) && + if (curr->type.isConcrete() && !curr->is<Unary>()) { // but not if it's already unary for (auto* child : ChildIterator(curr)) { if (child->type == curr->type) { continue; // already tried } - if (!isConcreteType(child->type)) { + if (!child->type.isConcrete()) { continue; // no conversion } Expression* fixed = nullptr; @@ -870,7 +870,7 @@ struct Reducer auto funcResult = func->result; auto* funcBody = func->body; for (auto* child : ChildIterator(func->body)) { - if (!(isConcreteType(child->type) || child->type == none)) { + if (!(child->type.isConcrete() || child->type == none)) { continue; // not something a function can return } // Try to replace the body with the child, fixing up the function diff --git a/src/wasm-builder.h b/src/wasm-builder.h index d47ec413f..87b1f0306 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -325,7 +325,7 @@ public: ret->value = value; ret->valueType = type; ret->finalize(); - assert(isConcreteType(ret->value->type) ? ret->value->type == type : true); + assert(ret->value->type.isConcrete() ? ret->value->type == type : true); return ret; } Store* makeAtomicStore(unsigned bytes, @@ -596,7 +596,7 @@ public: static Index addVar(Function* func, Name name, Type type) { // always ok to add a var, it does not affect other indices - assert(isConcreteType(type)); + assert(type.isConcrete()); Index index = func->getNumLocals(); if (name.is()) { func->localIndices[name] = index; @@ -701,7 +701,7 @@ public: // Drop an expression if it has a concrete type Expression* dropIfConcretelyTyped(Expression* curr) { - if (!isConcreteType(curr->type)) { + if (!curr->type.isConcrete()) { return curr; } return makeDrop(curr); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index dc2696716..e17b4e939 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -141,11 +141,11 @@ public: } auto ret = OverriddenVisitor<SubType, Flow>::visit(curr); if (!ret.breaking() && - (isConcreteType(curr->type) || isConcreteType(ret.value.type))) { + (curr->type.isConcrete() || ret.value.type.isConcrete())) { #if 1 // def WASM_INTERPRETER_DEBUG if (ret.value.type != curr->type) { - std::cerr << "expected " << printType(curr->type) << ", seeing " - << printType(ret.value.type) << " from\n" + std::cerr << "expected " << curr->type << ", seeing " << ret.value.type + << " from\n" << curr << '\n'; } #endif @@ -482,10 +482,10 @@ public: } Literal right = flow.value; NOTE_EVAL2(left, right); - assert(isConcreteType(curr->left->type) ? left.type == curr->left->type - : true); - assert(isConcreteType(curr->right->type) ? right.type == curr->right->type - : true); + assert(curr->left->type.isConcrete() ? left.type == curr->left->type + : true); + assert(curr->right->type.isConcrete() ? right.type == curr->right->type + : true); switch (curr->op) { case AddInt32: case AddInt64: @@ -1451,9 +1451,8 @@ private: assert(function->isParam(i)); if (function->params[i] != arguments[i].type) { std::cerr << "Function `" << function->name << "` expects type " - << printType(function->params[i]) << " for parameter " - << i << ", got " << printType(arguments[i].type) << "." - << std::endl; + << function->params[i] << " for parameter " << i + << ", got " << arguments[i].type << "." << std::endl; WASM_UNREACHABLE(); } locals[i] = arguments[i]; diff --git a/src/wasm-type.h b/src/wasm-type.h index a7c9f645b..371d3216f 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -18,15 +18,19 @@ #define wasm_wasm_type_h #include "wasm-features.h" +#include <ostream> +#include <vector> namespace wasm { class Type { uint32_t id; + void init(const std::vector<Type>&); public: enum ValueType : uint32_t { none, + unreachable, i32, i64, f32, @@ -34,10 +38,7 @@ public: v128, anyref, exnref, - // none means no type, e.g. a block can have no return type. but unreachable - // is different, as it can be "ignored" when doing type checking across - // branches - unreachable + _last_value_type, }; Type() = default; @@ -48,21 +49,54 @@ public: // But converting raw uint32_t is more dangerous, so make it explicit constexpr explicit Type(uint32_t id) : id(id){}; + // Construct from lists of elementary types + Type(std::initializer_list<Type> types); + explicit Type(const std::vector<Type>& types); + + // Accessors + size_t size() const; + const std::vector<Type>& expand() const; + + // Predicates + bool isSingle() const { return id >= i32 && id < _last_value_type; } + bool isMulti() const { return id >= _last_value_type; } + bool isConcrete() const { return id >= i32; } + bool isInteger() const { return id == i32 || id == i64; } + bool isFloat() const { return id == f32 || id == f64; } + bool isVector() const { return id == v128; }; + bool isRef() const { return id == anyref || id == exnref; } + // (In)equality must be defined for both Type and ValueType because it is // otherwise ambiguous whether to convert both this and other to int or // convert other to Type. - bool operator==(const Type& other) { return id == other.id; } + bool operator==(const Type& other) const { return id == other.id; } + bool operator==(const ValueType& other) const { return id == other; } + bool operator!=(const Type& other) const { return id != other.id; } + bool operator!=(const ValueType& other) const { return id != other; } - bool operator==(const ValueType& other) { return id == other; } - - bool operator!=(const Type& other) { return id != other.id; } + // Allows for using Types in switch statements + constexpr operator uint32_t() const { return id; } + std::string toString() const; +}; - bool operator!=(const ValueType& other) { return id != other; } +// Wrapper type for formatting types as "(param i32 i64 f32)" +struct ParamType { + Type type; + ParamType(Type type) : type(type) {} + std::string toString() const; +}; - // Allows for using Types in switch statements - constexpr operator int() const { return id; } +// Wrapper type for formatting types as "(result i32 i64 f32)" +struct ResultType { + Type type; + ResultType(Type type) : type(type) {} + std::string toString() const; }; +std::ostream& operator<<(std::ostream& os, Type t); +std::ostream& operator<<(std::ostream& os, ParamType t); +std::ostream& operator<<(std::ostream& os, ResultType t); + constexpr Type none = Type::none; constexpr Type i32 = Type::i32; constexpr Type i64 = Type::i64; @@ -73,16 +107,9 @@ constexpr Type anyref = Type::anyref; constexpr Type exnref = Type::exnref; constexpr Type unreachable = Type::unreachable; -const char* printType(Type type); unsigned getTypeSize(Type type); FeatureSet getFeatures(Type type); Type getType(unsigned size, bool float_); -Type getReachableType(Type a, Type b); -bool isConcreteType(Type type); -bool isFloatType(Type type); -bool isIntegerType(Type type); -bool isVectorType(Type type); -bool isReferenceType(Type type); Type reinterpretType(Type type); } // namespace wasm diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 8d8868eb4..41d8d2924 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -252,7 +252,7 @@ void Literal::printVec128(std::ostream& o, const std::array<uint8_t, 16>& v) { } std::ostream& operator<<(std::ostream& o, Literal literal) { - prepareMinorColor(o) << printType(literal.type) << ".const "; + prepareMinorColor(o) << literal.type << ".const "; switch (literal.type) { case Type::none: o << "?"; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 8d6721c9c..8f2fe3fe7 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1098,7 +1098,7 @@ Type WasmBinaryBuilder::getType() { Type WasmBinaryBuilder::getConcreteType() { auto type = getType(); - if (!isConcreteType(type)) { + if (!type.isConcrete()) { throw ParseException("non-concrete type when one expected"); } return type; @@ -1860,7 +1860,7 @@ Expression* WasmBinaryBuilder::popNonVoidExpression() { } requireFunctionContext("popping void where we need a new local"); auto type = block->list[0]->type; - if (isConcreteType(type)) { + if (type.isConcrete()) { auto local = builder.addVar(currFunction, type); block->list[0] = builder.makeLocalSet(local, block->list[0]); block->list.push_back(builder.makeLocalGet(local, type)); @@ -2417,7 +2417,7 @@ void WasmBinaryBuilder::pushBlockElements(Block* curr, if (i < end - 1) { // stacky&unreachable code may introduce elements that need to be dropped // in non-final positions - if (isConcreteType(item->type)) { + if (item->type.isConcrete()) { curr->list.back() = Builder(wasm).makeDrop(item); if (consumable == NONE) { // this is the first, and hence consumable value. note the location diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 39383a478..9d0f9b108 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -14,38 +14,193 @@ * limitations under the License. */ +#include <cassert> +#include <shared_mutex> +#include <sstream> +#include <unordered_map> + #include "wasm-type.h" #include "wasm-features.h" #include "compiler-support.h" -#include <cstdlib> + +template<> class std::hash<std::vector<wasm::Type>> { +public: + size_t operator()(const std::vector<wasm::Type>& types) const { + size_t res = 0; + for (auto vt : types) { + res ^= std::hash<uint32_t>{}(vt); + } + return res; + } +}; namespace wasm { -const char* printType(Type type) { +namespace { + +// TODO: switch to std::shared_mutex in C++17 +std::shared_timed_mutex mutex; + +std::vector<std::unique_ptr<std::vector<Type>>> typeLists = [] { + std::vector<std::unique_ptr<std::vector<Type>>> lists; + + auto add = [&](std::initializer_list<Type> types) { + return lists.push_back(std::make_unique<std::vector<Type>>(types)); + }; + + add({}); + add({}); + add({Type::i32}); + add({Type::i64}); + add({Type::f32}); + add({Type::f64}); + add({Type::v128}); + add({Type::anyref}); + add({Type::exnref}); + return lists; +}(); + +std::unordered_map<std::vector<Type>, uint32_t> indices = { + {{}, Type::none}, + {{}, Type::unreachable}, + {{Type::i32}, Type::i32}, + {{Type::i64}, Type::i64}, + {{Type::f32}, Type::f32}, + {{Type::f64}, Type::f64}, + {{Type::v128}, Type::v128}, + {{Type::anyref}, Type::anyref}, + {{Type::exnref}, Type::exnref}, +}; + +} // anonymous namespace + +void Type::init(const std::vector<Type>& types) { +#ifndef NDEBUG + for (Type t : types) { + assert(t.isSingle() && t.isConcrete()); + } +#endif + + auto lookup = [&]() { + auto indexIt = indices.find(types); + if (indexIt != indices.end()) { + id = indexIt->second; + return true; + } else { + return false; + } + }; + + { + // Try to look up previously interned type + std::shared_lock<std::shared_timed_mutex> lock(mutex); + if (lookup()) { + return; + } + } + { + // Add a new type if it hasn't been added concurrently + std::lock_guard<std::shared_timed_mutex> lock(mutex); + if (lookup()) { + return; + } + id = typeLists.size(); + typeLists.push_back(std::make_unique<std::vector<Type>>(types)); + indices[types] = id; + } +} + +Type::Type(std::initializer_list<Type> types) { init(types); } + +Type::Type(const std::vector<Type>& types) { init(types); } + +size_t Type::size() const { return expand().size(); } + +const std::vector<Type>& Type::expand() const { + std::shared_lock<std::shared_timed_mutex> lock(mutex); + assert(id < typeLists.size()); + return *typeLists[id].get(); +} + +namespace { + +std::ostream& +printPrefixedTypes(std::ostream& os, const char* prefix, Type type) { + os << '(' << prefix; + for (auto t : type.expand()) { + os << " " << t; + } + os << ')'; + return os; +} + +template<typename T> std::string genericToString(const T& t) { + std::ostringstream ss; + ss << t; + return ss.str(); +} + +} // anonymous namespace + +std::ostream& operator<<(std::ostream& os, Type type) { switch (type) { case Type::none: - return "none"; + os << "none"; + break; + case Type::unreachable: + os << "unreachable"; + break; case Type::i32: - return "i32"; + os << "i32"; + break; case Type::i64: - return "i64"; + os << "i64"; + break; case Type::f32: - return "f32"; + os << "f32"; + break; case Type::f64: - return "f64"; + os << "f64"; + break; case Type::v128: - return "v128"; + os << "v128"; + break; case Type::anyref: - return "anyref"; + os << "anyref"; + break; case Type::exnref: - return "exnref"; - case Type::unreachable: - return "unreachable"; + os << "exnref"; + break; + default: { + os << '('; + const std::vector<Type>& types = type.expand(); + for (size_t i = 0; i < types.size(); ++i) { + os << types[i]; + if (i < types.size() - 1) { + os << ", "; + } + } + os << ')'; + } } - WASM_UNREACHABLE(); + return os; } +std::ostream& operator<<(std::ostream& os, ParamType param) { + return printPrefixedTypes(os, "param", param.type); +} + +std::ostream& operator<<(std::ostream& os, ResultType param) { + return printPrefixedTypes(os, "result", param.type); +} + +std::string Type::toString() const { return genericToString(*this); } + +std::string ParamType::toString() const { return genericToString(*this); } + +std::string ResultType::toString() const { return genericToString(*this); } + unsigned getTypeSize(Type type) { switch (type) { case Type::i32: @@ -96,42 +251,6 @@ Type getType(unsigned size, bool float_) { WASM_UNREACHABLE(); } -Type getReachableType(Type a, Type b) { return a != unreachable ? a : b; } - -bool isConcreteType(Type type) { return type != none && type != unreachable; } - -bool isIntegerType(Type type) { - switch (type) { - case i32: - case i64: - return true; - default: - return false; - } -} - -bool isFloatType(Type type) { - switch (type) { - case f32: - case f64: - return true; - default: - return false; - } -} - -bool isVectorType(Type type) { return type == v128; } - -bool isReferenceType(Type type) { - switch (type) { - case anyref: - case exnref: - return true; - default: - return false; - } -} - Type reinterpretType(Type type) { switch (type) { case Type::i32: diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 82cd6d6f0..05cb45d25 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -352,7 +352,7 @@ void FunctionValidator::visitBlock(Block* curr) { assert(iter != breakInfos.end()); // we set it ourselves auto& info = iter->second; if (info.hasBeenSet()) { - if (isConcreteType(curr->type)) { + if (curr->type.isConcrete()) { shouldBeTrue(info.arity != 0, curr, "break arities must be > 0 if block has a value"); @@ -363,15 +363,14 @@ void FunctionValidator::visitBlock(Block* curr) { } // none or unreachable means a poison value that we should ignore - if // consumed, it will error - if (isConcreteType(info.type) && isConcreteType(curr->type)) { + if (info.type.isConcrete() && curr->type.isConcrete()) { shouldBeEqual( curr->type, info.type, curr, "block+breaks must have right type if breaks return a value"); } - if (isConcreteType(curr->type) && info.arity && - info.type != unreachable) { + if (curr->type.isConcrete() && info.arity && info.type != unreachable) { shouldBeEqual(curr->type, info.type, curr, @@ -381,7 +380,7 @@ void FunctionValidator::visitBlock(Block* curr) { info.arity != BreakInfo::PoisonArity, curr, "break arities must match"); if (curr->list.size() > 0) { auto last = curr->list.back()->type; - if (isConcreteType(last) && info.type != unreachable) { + if (last.isConcrete() && info.type != unreachable) { shouldBeEqual(last, info.type, curr, @@ -401,7 +400,7 @@ void FunctionValidator::visitBlock(Block* curr) { if (curr->list.size() > 1) { for (Index i = 0; i < curr->list.size() - 1; i++) { if (!shouldBeTrue( - !isConcreteType(curr->list[i]->type), + !curr->list[i]->type.isConcrete(), curr, "non-final block elements returning a value must be drop()ed " "(binaryen's autodrop option might help you)") && @@ -414,13 +413,13 @@ void FunctionValidator::visitBlock(Block* curr) { } if (curr->list.size() > 0) { auto backType = curr->list.back()->type; - if (!isConcreteType(curr->type)) { - shouldBeFalse(isConcreteType(backType), + if (!curr->type.isConcrete()) { + shouldBeFalse(backType.isConcrete(), curr, "if block is not returning a value, final element should " "not flow out a value"); } else { - if (isConcreteType(backType)) { + if (backType.isConcrete()) { shouldBeEqual( curr->type, backType, @@ -435,7 +434,7 @@ void FunctionValidator::visitBlock(Block* curr) { } } } - if (isConcreteType(curr->type)) { + if (curr->type.isConcrete()) { shouldBeTrue( curr->list.size() > 0, curr, "block with a value must not be empty"); } @@ -454,7 +453,7 @@ void FunctionValidator::visitLoop(Loop* curr) { breakInfos.erase(iter); } if (curr->type == none) { - shouldBeFalse(isConcreteType(curr->body->type), + shouldBeFalse(curr->body->type.isConcrete(), curr, "bad body for a loop that has no value"); } @@ -466,7 +465,7 @@ void FunctionValidator::visitIf(If* curr) { curr, "if condition must be valid"); if (!curr->ifFalse) { - shouldBeFalse(isConcreteType(curr->ifTrue->type), + shouldBeFalse(curr->ifTrue->type.isConcrete(), curr, "if without else must not return a value in body"); if (curr->condition->type != unreachable) { @@ -499,7 +498,7 @@ void FunctionValidator::visitIf(If* curr) { "unreachable if-else must have unreachable false"); } } - if (isConcreteType(curr->ifTrue->type)) { + if (curr->ifTrue->type.isConcrete()) { shouldBeEqual(curr->type, curr->ifTrue->type, curr, @@ -509,7 +508,7 @@ void FunctionValidator::visitIf(If* curr) { curr, "other arm must match concrete ifTrue"); } - if (isConcreteType(curr->ifFalse->type)) { + if (curr->ifFalse->type.isConcrete()) { shouldBeEqual(curr->type, curr->ifFalse->type, curr, @@ -705,7 +704,7 @@ void FunctionValidator::visitLocalGet(LocalGet* curr) { shouldBeTrue(curr->index < getFunction()->getNumLocals(), curr, "local.get index must be small enough"); - shouldBeTrue(isConcreteType(curr->type), + shouldBeTrue(curr->type.isConcrete(), curr, "local.get must have a valid type - check what you provided " "when you constructed the node"); @@ -1626,7 +1625,7 @@ void FunctionValidator::visitSelect(Select* curr) { } void FunctionValidator::visitDrop(Drop* curr) { - shouldBeTrue(isConcreteType(curr->value->type) || + shouldBeTrue(curr->value->type.isConcrete() || curr->value->type == unreachable, curr, "can only drop a valid value"); @@ -1678,14 +1677,14 @@ void FunctionValidator::visitTry(Try* curr) { curr->catchBody, "try's type does not match catch's body type"); } - if (isConcreteType(curr->body->type)) { + if (curr->body->type.isConcrete()) { shouldBeEqualOrFirstIsUnreachable( curr->catchBody->type, curr->body->type, curr->catchBody, "try's body type must match catch's body type"); } - if (isConcreteType(curr->catchBody->type)) { + if (curr->catchBody->type.isConcrete()) { shouldBeEqualOrFirstIsUnreachable( curr->body->type, curr->catchBody->type, @@ -1757,11 +1756,11 @@ void FunctionValidator::visitFunction(Function* curr) { FeatureSet typeFeatures = getFeatures(curr->result); for (auto type : curr->params) { typeFeatures |= getFeatures(type); - shouldBeTrue(isConcreteType(type), curr, "params must be concretely typed"); + shouldBeTrue(type.isConcrete(), curr, "params must be concretely typed"); } for (auto type : curr->vars) { typeFeatures |= getFeatures(type); - shouldBeTrue(isConcreteType(type), curr, "vars must be concretely typed"); + shouldBeTrue(type.isConcrete(), curr, "vars must be concretely typed"); } shouldBeTrue(typeFeatures <= getModule()->features, curr, @@ -1895,11 +1894,11 @@ static void validateBinaryenIR(Module& wasm, ValidationInfo& info) { // // The block has an added type, not derived from the ast itself, so it // is ok for it to be either i32 or unreachable. - if (!(isConcreteType(oldType) && newType == unreachable)) { + if (!(oldType.isConcrete() && newType == unreachable)) { std::ostringstream ss; ss << "stale type found in " << scope << " on " << curr - << "\n(marked as " << printType(oldType) << ", should be " - << printType(newType) << ")\n"; + << "\n(marked as " << oldType << ", should be " << newType + << ")\n"; info.fail(ss.str(), curr, getFunction()); } curr->type = oldType; @@ -2121,7 +2120,7 @@ static void validateEvents(Module& module, ValidationInfo& info) { curr->attribute, "Currently only attribute 0 is supported"); for (auto type : curr->params) { - info.shouldBeTrue(isIntegerType(type) || isFloatType(type), + info.shouldBeTrue(type.isInteger() || type.isFloat(), curr->name, "Values in an event should have integer or float type"); } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 72fed6c7b..b137044b2 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -283,7 +283,7 @@ static void handleUnreachable(Block* block, // 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 (isConcreteType(block->type)) { + if (block->type.isConcrete()) { return; } // look for an unreachable child @@ -314,7 +314,7 @@ void Block::finalize() { // (return) // (i32.const 10) // ) - if (isConcreteType(type)) { + if (type.isConcrete()) { return; } // if we are unreachable, we are done @@ -367,9 +367,9 @@ void If::finalize() { if (ifFalse) { if (ifTrue->type == ifFalse->type) { type = ifTrue->type; - } else if (isConcreteType(ifTrue->type) && ifFalse->type == unreachable) { + } else if (ifTrue->type.isConcrete() && ifFalse->type == unreachable) { type = ifTrue->type; - } else if (isConcreteType(ifFalse->type) && ifTrue->type == unreachable) { + } else if (ifFalse->type.isConcrete() && ifTrue->type == unreachable) { type = ifFalse->type; } else { type = none; @@ -895,9 +895,9 @@ void Host::finalize() { void Try::finalize() { if (body->type == catchBody->type) { type = body->type; - } else if (isConcreteType(body->type) && catchBody->type == unreachable) { + } else if (body->type.isConcrete() && catchBody->type == unreachable) { type = body->type; - } else if (isConcreteType(catchBody->type) && body->type == unreachable) { + } else if (catchBody->type.isConcrete() && body->type == unreachable) { type = catchBody->type; } else { type = none; diff --git a/src/wasm2js.h b/src/wasm2js.h index 337ea3806..344416b54 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -155,7 +155,7 @@ public: frees[type].pop_back(); } else { size_t index = temps[type]++; - ret = IString((std::string("wasm2js_") + printType(type) + "$" + + ret = IString((std::string("wasm2js_") + type.toString() + "$" + std::to_string(index)) .c_str(), false); |