diff options
59 files changed, 4275 insertions, 363 deletions
@@ -125,16 +125,29 @@ for asm in tests: cmd += ['--wasm-only'] wasm = os.path.join(options.binaryen_test, wasm) print '..', asm, wasm - actual = run_command(cmd) - # verify output - if not os.path.exists(wasm): - fail_with_error('output .wast file %s does not exist' % wasm) - expected = open(wasm, 'rb').read() - if actual != expected: - fail(actual, expected) - - binary_format_check(wasm, verify_final_result=False) + def do_asm2wasm_test(): + actual = run_command(cmd) + + # verify output + if not os.path.exists(wasm): + fail_with_error('output .wast file %s does not exist' % wasm) + expected = open(wasm, 'rb').read() + if actual != expected: + fail(actual, expected) + + binary_format_check(wasm, verify_final_result=False) + + # test both normally and with pass debug (so each inter-pass state is validated) + old_pass_debug = os.environ.get('BINARYEN_PASS_DEBUG') + try: + os.environ['BINARYEN_PASS_DEBUG'] = '1' + do_asm2wasm_test() + del os.environ['BINARYEN_PASS_DEBUG'] + do_asm2wasm_test() + finally: + if old_pass_debug is not None: + os.environ['BINARYEN_PASS_DEBUG'] = old_pass_debug # verify in wasm if options.interpreter: @@ -280,6 +293,11 @@ for t in spec_tests: cmd = cmd + (extra.get(os.path.basename(wast)) or []) return run_command(cmd, stderr=subprocess.PIPE) + def run_opt_test(wast): + # check optimization validation + cmd = WASM_OPT + [wast, '-O'] + run_command(cmd) + def check_expected(actual, expected): if expected and os.path.exists(expected): expected = open(expected).read() @@ -335,6 +353,7 @@ for t in spec_tests: split_num += 1 with open('split.wast', 'w') as o: o.write(module + '\n' + '\n'.join(asserts)) run_spec_test('split.wast') # before binary stuff - just check it's still ok split out + run_opt_test('split.wast') # also that our optimizer doesn't break on it result_wast = binary_format_check('split.wast', verify_final_result=False) # add the asserts, and verify that the test still passes open(result_wast, 'a').write('\n' + '\n'.join(asserts)) diff --git a/src/asm2wasm.h b/src/asm2wasm.h index 6327920fe..16e7c95e1 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -35,7 +35,6 @@ #include "wasm-builder.h" #include "wasm-emscripten.h" #include "wasm-printing.h" -#include "wasm-validator.h" #include "wasm-module-building.h" namespace wasm { @@ -1339,6 +1338,12 @@ void Asm2WasmBuilder::processAsm(Ref ast) { add->left = parent->builder.makeConst(Literal((int32_t)parent->functionTableStarts[tableName])); } } + + void visitFunction(Function* curr) { + // changing call types requires we percolate types, and drop stuff. + // we do this in this pass so that we don't look broken between passes + AutoDrop().walkFunctionInModule(curr, getModule()); + } }; // apply debug info, reducing intrinsic calls into annotations on the ast nodes @@ -1400,9 +1405,9 @@ void Asm2WasmBuilder::processAsm(Ref ast) { passRunner.setDebug(true); passRunner.setValidateGlobally(false); } + // finalizeCalls also does autoDrop, which is crucial for the non-optimizing case, + // so that the output of the first pass is valid passRunner.add<FinalizeCalls>(this); - passRunner.add<ReFinalize>(); // FinalizeCalls changes call types, need to percolate - passRunner.add<AutoDrop>(); // FinalizeCalls may cause us to require additional drops if (legalizeJavaScriptFFI) { passRunner.add("legalize-js-interface"); } @@ -1551,8 +1556,6 @@ void Asm2WasmBuilder::processAsm(Ref ast) { body->finalize(); func->body = body; } - - assert(WasmValidator().validate(wasm)); } Function* Asm2WasmBuilder::processFunction(Ref ast) { @@ -2489,7 +2492,6 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { } top->list.push_back(br); - top->finalize(); for (unsigned i = 0; i < cases->size(); i++) { Ref curr = cases[i]; @@ -2564,7 +2566,6 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { top->name = name; next->list.push_back(top); next->list.push_back(case_); - next->finalize(); top = next; nameMapper.popLabelName(name); } @@ -2580,7 +2581,6 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { first->ifFalse = builder.makeBreak(br->default_); brHolder->list.push_back(chain); - brHolder->finalize(); } breakStack.pop_back(); diff --git a/src/ast/ExpressionManipulator.cpp b/src/ast/ExpressionManipulator.cpp index fb861f0d8..160ff55e1 100644 --- a/src/ast/ExpressionManipulator.cpp +++ b/src/ast/ExpressionManipulator.cpp @@ -147,6 +147,7 @@ void ExpressionManipulator::spliceIntoBlock(Block* block, Index index, Expressio } list[index] = add; } + block->finalize(block->type); } } // namespace wasm diff --git a/src/ast/block-utils.h b/src/ast/block-utils.h new file mode 100644 index 000000000..7b1b9dcff --- /dev/null +++ b/src/ast/block-utils.h @@ -0,0 +1,66 @@ +/* + * Copyright 2017 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. + */ + +#ifndef wasm_ast_block_h +#define wasm_ast_block_h + +#include "literal.h" +#include "wasm.h" +#include "ast_utils.h" + +namespace wasm { + +namespace BlockUtils { + // if a block has just one element, it can often be replaced + // with that content + template<typename T> + inline Expression* simplifyToContents(Block* block, T* parent, bool allowTypeChange = false) { + auto& list = block->list; + if (list.size() == 1 && !BreakSeeker::has(list[0], block->name)) { + // just one element. try to replace the block + auto* singleton = list[0]; + auto sideEffects = EffectAnalyzer(parent->getPassOptions(), singleton).hasSideEffects(); + if (!sideEffects && !isConcreteWasmType(singleton->type)) { + // 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); + } else if (block->type == singleton->type || allowTypeChange) { + return singleton; + } else { + // (side effects +) type change, must be block with declared value but 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(isConcreteWasmType(block->type) && singleton->type == unreachable); + // we could replace with unreachable, but would need to update all + // the parent's types + } + } else if (list.size() == 0) { + ExpressionManipulator::nop(block); + } + return block; + } + + // similar, but when we allow the type to change while doing so + template<typename T> + inline Expression* simplifyToContentsWithPossibleTypeChange(Block* block, T* parent) { + return simplifyToContents(block, parent, true); + } +}; + +} // namespace wasm + +#endif // wasm_ast_block_h + diff --git a/src/ast/manipulation.h b/src/ast/manipulation.h new file mode 100644 index 000000000..9e01e4c74 --- /dev/null +++ b/src/ast/manipulation.h @@ -0,0 +1,69 @@ +/* + * Copyright 2017 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. + */ + +#ifndef wasm_ast_manipulation_h +#define wasm_ast_manipulation_h + +#include "wasm.h" + +namespace wasm { + +struct ExpressionManipulator { + // Re-use a node's memory. This helps avoid allocation when optimizing. + template<typename InputType, typename OutputType> + static OutputType* convert(InputType *input) { + static_assert(sizeof(OutputType) <= sizeof(InputType), + "Can only convert to a smaller size Expression node"); + input->~InputType(); // arena-allocaed, so no destructor, but avoid UB. + OutputType* output = (OutputType*)(input); + new (output) OutputType; + return output; + } + + // Convenience method for nop, which is a common conversion + template<typename InputType> + static Nop* nop(InputType* target) { + return convert<InputType, Nop>(target); + } + + // Convert a node that allocates + template<typename InputType, typename OutputType> + static OutputType* convert(InputType *input, MixedArena& allocator) { + assert(sizeof(OutputType) <= sizeof(InputType)); + input->~InputType(); // arena-allocaed, so no destructor, but avoid UB. + OutputType* output = (OutputType*)(input); + new (output) OutputType(allocator); + return output; + } + + using CustomCopier = std::function<Expression*(Expression*)>; + static Expression* flexibleCopy(Expression* original, Module& wasm, CustomCopier custom); + + static Expression* copy(Expression* original, Module& wasm) { + auto copy = [](Expression* curr) { + return nullptr; + }; + return flexibleCopy(original, wasm, copy); + } + + // Splice an item into the middle of a block's list + static void spliceIntoBlock(Block* block, Index index, Expression* add); +}; + +} // wasm + +#endif // wams_ast_manipulation_h + diff --git a/src/ast/type-updating.h b/src/ast/type-updating.h new file mode 100644 index 000000000..b8988761a --- /dev/null +++ b/src/ast/type-updating.h @@ -0,0 +1,282 @@ +/* + * Copyright 2017 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. + */ + +#ifndef wasm_ast_type_updating_h +#define wasm_ast_type_updating_h + +#include "wasm-traversal.h" + +namespace wasm { + +// a class that tracks type dependencies between nodes, letting you +// update types efficiently when removing and altering code. +// altering code can alter types in the following way: +// * removing a break can make a block unreachable, if nothing else +// reaches it +// * altering the type of a child to unreachable can make the parent +// unreachable +struct TypeUpdater : public ExpressionStackWalker<TypeUpdater, UnifiedExpressionVisitor<TypeUpdater>> { + // Part 1: Scanning + + // track names to their blocks, so that when we remove a break to + // a block, we know how to find it if we need to update it + struct BlockInfo { + Block* block = nullptr; + int numBreaks = 0; + }; + std::map<Name, BlockInfo> blockInfos; + + // track the parent of each node, as child type changes may lead to + // unreachability + std::map<Expression*, Expression*> parents; + + void visitExpression(Expression* curr) { + if (expressionStack.size() > 1) { + parents[curr] = expressionStack[expressionStack.size() - 2]; + } else { + parents[curr] = nullptr; // this is the top level + } + // discover block/break relationships + if (auto* block = curr->dynCast<Block>()) { + if (block->name.is()) { + blockInfos[block->name].block = block; + } + } else if (auto* br = curr->dynCast<Break>()) { + // ensure info exists, discoverBreaks can then fill it + blockInfos[br->name]; + } else if (auto* sw = curr->dynCast<Switch>()) { + // ensure info exists, discoverBreaks can then fill it + for (auto target : sw->targets) { + blockInfos[target]; + } + blockInfos[sw->default_]; + } + // add a break to the info, for break and switch + discoverBreaks(curr, +1); + } + + // Part 2: Updating + + // Node replacements, additions, removals and type changes should be noted. An + // exception is nodes you know will never be looked at again. + + // note the replacement of one node with another. this should be called + // after performing the replacement. + // this does *not* look into the node. you should do so yourself if necessary + void noteReplacement(Expression* from, Expression* to) { + auto parent = parents[from]; + noteRemoval(from); + // if we are replacing with a child, i.e. a node that was already present + // in the ast, then we just have a type and parent to update + if (parents.find(to) != parents.end()) { + parents[to] = parent; + if (from->type != to->type) { + propagateTypesUp(to); + } + } else { + noteAddition(to, parent, from); + } + } + + // note the removal of a node + void noteRemoval(Expression* curr) { + noteRemovalOrAddition(curr, nullptr); + parents.erase(curr); + } + + // note the removal of a node and all its children + void noteRecursiveRemoval(Expression* curr) { + struct Recurser : public PostWalker<Recurser, UnifiedExpressionVisitor<Recurser>> { + TypeUpdater& parent; + + Recurser(TypeUpdater& parent, Expression* root) : parent(parent) { + walk(root); + } + + void visitExpression(Expression* curr) { + parent.noteRemoval(curr); + } + }; + + Recurser(*this, curr); + } + + void noteAddition(Expression* curr, Expression* parent, Expression* previous = nullptr) { + assert(parents.find(curr) == parents.end()); // must not already exist + noteRemovalOrAddition(curr, parent); + // if we didn't replace with the exact same type, propagate types up + if (!(previous && previous->type == curr->type)) { + propagateTypesUp(curr); + } + } + + // if parent is nullptr, this is a removal + void noteRemovalOrAddition(Expression* curr, Expression* parent) { + parents[curr] = parent; + discoverBreaks(curr, parent ? +1 : -1); + } + + // adds (or removes) breaks depending on break/switch contents + void discoverBreaks(Expression* curr, int change) { + if (auto* br = curr->dynCast<Break>()) { + if (!(br->value && br->value->type == unreachable) && + !(br->condition && br->condition->type == unreachable)) { + noteBreakChange(br->name, change, br->value); + } + } else if (auto* sw = curr->dynCast<Switch>()) { + if (!(sw->value && sw->value->type == unreachable) && + sw->condition->type != unreachable) { + applySwitchChanges(sw, change); + } + } + } + + void applySwitchChanges(Switch* sw, int change) { + std::set<Name> seen; + for (auto target : sw->targets) { + if (seen.insert(target).second) { + noteBreakChange(target, change, sw->value); + } + } + if (seen.insert(sw->default_).second) { + noteBreakChange(sw->default_, change, sw->value); + } + } + + // note the addition of a node + void noteBreakChange(Name name, int change, Expression* value) { + auto iter = blockInfos.find(name); + if (iter == blockInfos.end()) { + return; // we can ignore breaks to loops + } + auto& info = iter->second; + info.numBreaks += change; + assert(info.numBreaks >= 0); + auto* block = info.block; + if (block) { // if to a loop, can ignore + if (info.numBreaks == 0) { + // dropped to 0! the block may now be unreachable. that + // requires that it doesn't have a fallthrough + makeBlockUnreachableIfNoFallThrough(block); + } else if (change == 1 && info.numBreaks == 1) { + // bumped to 1! the block may now be reachable + if (block->type != unreachable) { + return; // was already reachable, had a fallthrough + } + changeTypeTo(block, value ? value->type : none); + } + } + } + + // alters the type of a node to a new type. + // this propagates the type change through all the parents. + void changeTypeTo(Expression* curr, WasmType newType) { + if (curr->type == newType) return; // nothing to do + curr->type = newType; + propagateTypesUp(curr); + } + + // given a node that has a new type, or is a new node, update + // all the parents accordingly. the existence of the node and + // any changes to it already occurred, this just updates the + // parents following that. i.e., nothing is done to the + // node we start on, it's done. + // the one thing we need to do here is propagate unreachability, + // no other change is possible + void propagateTypesUp(Expression* curr) { + if (curr->type != unreachable) return; + while (1) { + auto* child = curr; + curr = parents[child]; + if (!curr) return; + // if a child of a break/switch is now unreachable, the + // break may no longer be taken. note that if we get here, + // this is an actually new unreachable child of the + // node, so if there is just 1 such child, it is us, and + // we are newly unreachable + if (auto* br = curr->dynCast<Break>()) { + int unreachableChildren = 0; + if (br->value && br->value->type == unreachable) unreachableChildren++; + if (br->condition && br->condition->type == unreachable) unreachableChildren++; + if (unreachableChildren == 1) { + // the break is no longer taken + noteBreakChange(br->name, -1, br->value); + } + } else if (auto* sw = curr->dynCast<Switch>()) { + int unreachableChildren = 0; + if (sw->value && sw->value->type == unreachable) unreachableChildren++; + if (sw->condition->type == unreachable) unreachableChildren++; + if (unreachableChildren == 1) { + applySwitchChanges(sw, -1); + } + } + // get ready to apply unreachability to this node + if (curr->type == unreachable) { + return; // already unreachable, stop here + } + // most nodes become unreachable if a child is unreachable, + // but exceptions exists + if (auto* block = curr->dynCast<Block>()) { + // if the block has breaks, it can keep its type + if (!block->name.is() || blockInfos[block->name].numBreaks == 0) { + curr->type = unreachable; + } else { + return; // did not turn + } + } else if (auto* iff = curr->dynCast<If>()) { + // may not be unreachable if just one side is + iff->finalize(); + if (curr->type != unreachable) { + return; // did not turn + } + } else { + curr->type = unreachable; + } + } + } + + // efficiently update the type of a block, given the data we know. this + // can remove a concrete type and turn the block unreachable when it is + // unreachable, and it does this efficiently, without scanning the full + // contents + void maybeUpdateTypeToUnreachable(Block* curr) { + if (!isConcreteWasmType(curr->type)) { + return; // nothing concrete to change to unreachable + } + if (curr->name.is() && blockInfos[curr->name].numBreaks > 0) { + return;// has a break, not unreachable + } + // look for a fallthrough + makeBlockUnreachableIfNoFallThrough(curr); + } + + void makeBlockUnreachableIfNoFallThrough(Block* curr) { + if (curr->type == unreachable) { + return; // no change possible + } + for (auto* child : curr->list) { + if (child->type == unreachable) { + // no fallthrough, this block is now unreachable + changeTypeTo(curr, unreachable); + return; + } + } + } +}; + +} // namespace wasm + +#endif // wasm_ast_type_updating_h diff --git a/src/ast_utils.h b/src/ast_utils.h index 5a7c40630..aa4120569 100644 --- a/src/ast_utils.h +++ b/src/ast_utils.h @@ -42,10 +42,18 @@ struct BreakSeeker : public PostWalker<BreakSeeker> { } void visitBreak(Break *curr) { + // ignore an unreachable break + if (curr->condition && curr->condition->type == unreachable) return; + if (curr->value && curr->value->type == unreachable) return; + // check the break if (curr->name == target) noteFound(curr->value); } void visitSwitch(Switch *curr) { + // ignore an unreachable switch + if (curr->condition->type == unreachable) return; + if (curr->value && curr->value->type == unreachable) return; + // check the switch for (auto name : curr->targets) { if (name == target) noteFound(curr->value); } @@ -273,50 +281,6 @@ struct Measurer : public PostWalker<Measurer, UnifiedExpressionVisitor<Measurer> } }; -// Manipulate expressions - -struct ExpressionManipulator { - // Re-use a node's memory. This helps avoid allocation when optimizing. - template<typename InputType, typename OutputType> - static OutputType* convert(InputType *input) { - static_assert(sizeof(OutputType) <= sizeof(InputType), - "Can only convert to a smaller size Expression node"); - input->~InputType(); // arena-allocaed, so no destructor, but avoid UB. - OutputType* output = (OutputType*)(input); - new (output) OutputType; - return output; - } - - // Convenience method for nop, which is a common conversion - template<typename InputType> - static void nop(InputType* target) { - convert<InputType, Nop>(target); - } - - // Convert a node that allocates - template<typename InputType, typename OutputType> - static OutputType* convert(InputType *input, MixedArena& allocator) { - assert(sizeof(OutputType) <= sizeof(InputType)); - input->~InputType(); // arena-allocaed, so no destructor, but avoid UB. - OutputType* output = (OutputType*)(input); - new (output) OutputType(allocator); - return output; - } - - using CustomCopier = std::function<Expression*(Expression*)>; - static Expression* flexibleCopy(Expression* original, Module& wasm, CustomCopier custom); - - static Expression* copy(Expression* original, Module& wasm) { - auto copy = [](Expression* curr) { - return nullptr; - }; - return flexibleCopy(original, wasm, copy); - } - - // Splice an item into the middle of a block's list - static void spliceIntoBlock(Block* block, Index index, Expression* add); -}; - struct ExpressionAnalyzer { // Given a stack of expressions, checks if the topmost is used as a result. // For example, if the parent is a block and the node is before the last position, @@ -357,11 +321,102 @@ struct ExpressionAnalyzer { static uint32_t hash(Expression* curr); }; -// Finalizes a node - +// Re-Finalizes all node types +// This removes "unnecessary' block/if/loop types, i.e., that are added +// specifically, as in +// (block i32 (unreachable)) +// vs +// (block (unreachable)) +// This converts to the latter form. struct ReFinalize : public WalkerPass<PostWalker<ReFinalize>> { + bool isFunctionParallel() override { return true; } + + Pass* create() override { return new ReFinalize; } + ReFinalize() { name = "refinalize"; } + // block finalization is O(bad) if we do each block by itself, so do it in bulk, + // tracking break value types so we just do a linear pass + + std::map<Name, WasmType> breakValues; + + void visitBlock(Block *curr) { + // do this quickly, without any validation + if (curr->name.is()) { + auto iter = breakValues.find(curr->name); + if (iter != breakValues.end()) { + // there is a break to here + curr->type = iter->second; + return; + } + } + // nothing branches here + if (curr->list.size() > 0) { + // if we have an unreachable child, we are unreachable + // (we don't need to recurse into children, they can't + // break to us) + for (auto* child : curr->list) { + if (child->type == unreachable) { + curr->type = unreachable; + return; + } + } + // children are reachable, so last element determines type + curr->type = curr->list.back()->type; + } else { + curr->type = none; + } + } + void visitIf(If *curr) { curr->finalize(); } + void visitLoop(Loop *curr) { curr->finalize(); } + void visitBreak(Break *curr) { + curr->finalize(); + if (curr->value && curr->value->type == unreachable) { + return; // not an actual break + } + if (curr->condition && curr->condition->type == unreachable) { + return; // not an actual break + } + breakValues[curr->name] = getValueType(curr->value); + } + void visitSwitch(Switch *curr) { + curr->finalize(); + if (curr->condition->type == unreachable || (curr->value && curr->value->type == unreachable)) { + return; // not an actual break + } + auto valueType = getValueType(curr->value); + for (auto target : curr->targets) { + breakValues[target] = valueType; + } + breakValues[curr->default_] = valueType; + } + void visitCall(Call *curr) { curr->finalize(); } + void visitCallImport(CallImport *curr) { curr->finalize(); } + void visitCallIndirect(CallIndirect *curr) { curr->finalize(); } + void visitGetLocal(GetLocal *curr) { curr->finalize(); } + void visitSetLocal(SetLocal *curr) { curr->finalize(); } + void visitGetGlobal(GetGlobal *curr) { curr->finalize(); } + void visitSetGlobal(SetGlobal *curr) { curr->finalize(); } + void visitLoad(Load *curr) { curr->finalize(); } + void visitStore(Store *curr) { curr->finalize(); } + void visitConst(Const *curr) { curr->finalize(); } + void visitUnary(Unary *curr) { curr->finalize(); } + void visitBinary(Binary *curr) { curr->finalize(); } + void visitSelect(Select *curr) { curr->finalize(); } + void visitDrop(Drop *curr) { curr->finalize(); } + void visitReturn(Return *curr) { curr->finalize(); } + void visitHost(Host *curr) { curr->finalize(); } + void visitNop(Nop *curr) { curr->finalize(); } + void visitUnreachable(Unreachable *curr) { curr->finalize(); } + + WasmType getValueType(Expression* value) { + return value && value->type != unreachable ? value->type : none; + } +}; + +// Re-finalize a single node. This is slow, if you want to refinalize +// an entire ast, use ReFinalize +struct ReFinalizeNode : public Visitor<ReFinalizeNode> { void visitBlock(Block *curr) { curr->finalize(); } void visitIf(If *curr) { curr->finalize(); } void visitLoop(Loop *curr) { curr->finalize(); } @@ -385,10 +440,21 @@ struct ReFinalize : public WalkerPass<PostWalker<ReFinalize>> { void visitHost(Host *curr) { curr->finalize(); } void visitNop(Nop *curr) { curr->finalize(); } void visitUnreachable(Unreachable *curr) { curr->finalize(); } + + // given a stack of nested expressions, update them all from child to parent + static void updateStack(std::vector<Expression*>& expressionStack) { + for (int i = int(expressionStack.size()) - 1; i >= 0; i--) { + auto* curr = expressionStack[i]; + ReFinalizeNode().visit(curr); + } + } }; // Adds drop() operations where necessary. This lets you not worry about adding drop when // generating code. +// This also refinalizes before and after, as dropping can change types, and depends +// on types being cleaned up - no unnecessary block/if/loop types (see refinalize) +// TODO: optimize that, interleave them struct AutoDrop : public WalkerPass<ExpressionStackWalker<AutoDrop>> { bool isFunctionParallel() override { return true; } @@ -410,10 +476,7 @@ struct AutoDrop : public WalkerPass<ExpressionStackWalker<AutoDrop>> { } void reFinalize() { - for (int i = int(expressionStack.size()) - 1; i >= 0; i--) { - auto* curr = expressionStack[i]; - ReFinalize().visit(curr); - } + ReFinalizeNode::updateStack(expressionStack); } void visitBlock(Block* curr) { @@ -442,10 +505,13 @@ struct AutoDrop : public WalkerPass<ExpressionStackWalker<AutoDrop>> { } } - void visitFunction(Function* curr) { + void doWalkFunction(Function* curr) { + ReFinalize().walkFunctionInModule(curr, getModule()); + walk(curr->body); if (curr->result == none && isConcreteWasmType(curr->body->type)) { curr->body = Builder(*getModule()).makeDrop(curr->body); } + ReFinalize().walkFunctionInModule(curr, getModule()); } }; diff --git a/src/cfg/Relooper.cpp b/src/cfg/Relooper.cpp index a900c6608..9fcc9c8be 100644 --- a/src/cfg/Relooper.cpp +++ b/src/cfg/Relooper.cpp @@ -84,6 +84,7 @@ static wasm::Expression* HandleFollowupMultiples(wasm::Expression* Ret, Shape* P } else { for (auto* Entry : Loop->Entries) { Curr->name = Builder.getBlockBreakName(Entry->Id); + Curr->finalize(); auto* Outer = Builder.makeBlock(Curr); Outer->finalize(); // TODO: not really necessary Curr = Outer; @@ -210,6 +211,10 @@ wasm::Expression* Block::Render(RelooperBuilder& Builder, bool InLoop) { // We'll emit a chain of if-elses wasm::If* CurrIf = nullptr; + // we build an if, then add a child, then add a child to that, etc., so we must + // finalize them in reverse order + std::vector<wasm::If*> finalizeStack; + wasm::Expression* RemainingConditions = nullptr; for (BlockBranchMap::iterator iter = ProcessedBranchesOut.begin();; iter++) { @@ -244,6 +249,7 @@ wasm::Expression* Block::Render(RelooperBuilder& Builder, bool InLoop) { wasm::Expression* Now; if (RemainingConditions) { Now = Builder.makeIf(RemainingConditions, CurrContent); + finalizeStack.push_back(Now->cast<wasm::If>()); } else { Now = CurrContent; } @@ -256,6 +262,7 @@ wasm::Expression* Block::Render(RelooperBuilder& Builder, bool InLoop) { } } else { auto* Now = Builder.makeIf(Details->Condition, CurrContent); + finalizeStack.push_back(Now); if (!CurrIf) { assert(!Root); Root = CurrIf = Now; @@ -275,6 +282,14 @@ wasm::Expression* Block::Render(RelooperBuilder& Builder, bool InLoop) { } if (IsDefault) break; } + + // finalize the if-chains + while (finalizeStack.size() > 0) { + wasm::If* curr = finalizeStack.back(); + finalizeStack.pop_back(); + curr->finalize(); + } + } else { // Emit a switch auto Base = std::string("switch$") + std::to_string(Id); @@ -365,11 +380,13 @@ wasm::Expression* MultipleShape::Render(RelooperBuilder& Builder, bool InLoop) { // TODO: consider switch // emit an if-else chain wasm::If *FirstIf = nullptr, *CurrIf = nullptr; + std::vector<wasm::If*> finalizeStack; for (IdShapeMap::iterator iter = InnerMap.begin(); iter != InnerMap.end(); iter++) { auto* Now = Builder.makeIf( Builder.makeCheckLabel(iter->first), iter->second->Render(Builder, InLoop) ); + finalizeStack.push_back(Now); if (!CurrIf) { FirstIf = CurrIf = Now; } else { @@ -378,6 +395,11 @@ wasm::Expression* MultipleShape::Render(RelooperBuilder& Builder, bool InLoop) { CurrIf = Now; } } + while (finalizeStack.size() > 0) { + wasm::If* curr = finalizeStack.back(); + finalizeStack.pop_back(); + curr->finalize(); + } wasm::Expression* Ret = Builder.makeBlock(FirstIf); Ret = HandleFollowupMultiples(Ret, this, Builder, InLoop); if (Next) { diff --git a/src/pass.h b/src/pass.h index 513979954..198d5dcb5 100644 --- a/src/pass.h +++ b/src/pass.h @@ -131,6 +131,16 @@ struct PassRunner { isNested = nested; } + // BINARYEN_PASS_DEBUG is a convenient commandline way to log out the toplevel passes, their times, + // and validate between each pass. + // (we don't recurse pass debug into sub-passes, as it doesn't help anyhow and + // also is bad for e.g. printing which is a pass) + // this method returns whether we are in passDebug mode, and which value: + // 1: run pass by pass, validating in between + // 2: also save the last pass, so it breakage happens we can print the last one + // 3: also dump out byn-* files for each pass + static int getPassDebug(); + protected: bool isNested = false; diff --git a/src/passes/CoalesceLocals.cpp b/src/passes/CoalesceLocals.cpp index ed075c57f..2828c9955 100644 --- a/src/passes/CoalesceLocals.cpp +++ b/src/passes/CoalesceLocals.cpp @@ -159,7 +159,7 @@ struct CoalesceLocals : public WalkerPass<CFGWalker<CoalesceLocals, Visitor<Coal auto* curr = (*currp)->cast<GetLocal>(); // if in unreachable code, ignore if (!self->currBasicBlock) { - ExpressionManipulator::convert<GetLocal, Unreachable>(curr); + *currp = Builder(*self->getModule()).replaceWithIdenticalType(curr); return; } self->currBasicBlock->contents.actions.emplace_back(Action::Get, curr->index, currp); @@ -169,11 +169,7 @@ struct CoalesceLocals : public WalkerPass<CFGWalker<CoalesceLocals, Visitor<Coal auto* curr = (*currp)->cast<SetLocal>(); // if in unreachable code, ignore if (!self->currBasicBlock) { - if (curr->isTee()) { - ExpressionManipulator::convert<SetLocal, Unreachable>(curr); - } else { - ExpressionManipulator::nop(curr); - } + *currp = Builder(*self->getModule()).replaceWithIdenticalType(curr); return; } self->currBasicBlock->contents.actions.emplace_back(Action::Set, curr->index, currp); @@ -625,8 +621,9 @@ static void removeIfCopy(Expression** origin, SetLocal* set, If* iff, Expression // replace the origin with the if, and sink the set into the other non-copying arm *origin = iff; set->value = other; + set->finalize(); other = set; - if (!set->isTee()) { + if (!isConcreteWasmType(set->type)) { // we don't need the copy at all copy = nullptr; if (!iff->ifTrue) { diff --git a/src/passes/DeadCodeElimination.cpp b/src/passes/DeadCodeElimination.cpp index 0555ff1f0..42f86ba45 100644 --- a/src/passes/DeadCodeElimination.cpp +++ b/src/passes/DeadCodeElimination.cpp @@ -32,6 +32,8 @@ #include <pass.h> #include <ast_utils.h> #include <wasm-builder.h> +#include <ast/block-utils.h> +#include <ast/type-updating.h> namespace wasm { @@ -40,11 +42,23 @@ struct DeadCodeElimination : public WalkerPass<PostWalker<DeadCodeElimination>> Pass* create() override { return new DeadCodeElimination; } + // as we remove code, we must keep the types of other nodes valid + TypeUpdater typeUpdater; + + Expression* replaceCurrent(Expression* expression) { + auto* old = getCurrent(); + WalkerPass<PostWalker<DeadCodeElimination>>::replaceCurrent(expression); + // also update the type updater + typeUpdater.noteReplacement(old, expression); + return expression; + } + // whether the current code is actually reachable bool reachable; void doWalkFunction(Function* func) { reachable = true; + typeUpdater.walk(func->body); walk(func->body); } @@ -160,10 +174,8 @@ struct DeadCodeElimination : public WalkerPass<PostWalker<DeadCodeElimination>> // see https://github.com/WebAssembly/spec/issues/355 if (!(isConcreteWasmType(block->type) && block->list[i]->type == none)) { block->list.resize(i + 1); - // note that we do *not* finalize here. it is incorrect to re-finalize a block - // after removing elements, as it may no longer have branches to it that would - // determine its type, so re-finalizing would just wipe out an existing type - // that it had. + // note that we still walk the children, so typeUpdater will already + // note they are being removed, and we don't need to do that here } } } @@ -175,8 +187,11 @@ struct DeadCodeElimination : public WalkerPass<PostWalker<DeadCodeElimination>> reachable = reachable || reachableBreaks.count(curr->name); reachableBreaks.erase(curr->name); } - if (curr->list.size() == 1 && isDead(curr->list[0]) && !BreakSeeker::has(curr->list[0], curr->name)) { - replaceCurrent(curr->list[0]); + if (curr->list.size() == 1 && isDead(curr->list[0])) { + replaceCurrent(BlockUtils::simplifyToContentsWithPossibleTypeChange(curr, this)); + } else { + // the block may have had a type, but can now be unreachable, which allows more reduction outside + typeUpdater.maybeUpdateTypeToUnreachable(curr); } } @@ -213,13 +228,18 @@ struct DeadCodeElimination : public WalkerPass<PostWalker<DeadCodeElimination>> if (isDead(curr->condition)) { replaceCurrent(curr->condition); } + // the if may have had a type, but can now be unreachable, which allows more reduction outside + curr->finalize(); } static void scan(DeadCodeElimination* self, Expression** currp) { if (!self->reachable) { // convert to an unreachable. do this without UB, even though we have no destructors on AST nodes - #define DELEGATE(CLASS_TO_VISIT) \ - { ExpressionManipulator::convert<CLASS_TO_VISIT, Unreachable>(static_cast<CLASS_TO_VISIT*>(*currp)); break; } + #define DELEGATE(CLASS_TO_VISIT) { \ + self->typeUpdater.noteRecursiveRemoval(*currp); \ + ExpressionManipulator::convert<CLASS_TO_VISIT, Unreachable>(static_cast<CLASS_TO_VISIT*>(*currp)); \ + break; \ + } switch ((*currp)->_id) { case Expression::Id::BlockId: DELEGATE(Block); case Expression::Id::IfId: DELEGATE(If); @@ -398,6 +418,12 @@ struct DeadCodeElimination : public WalkerPass<PostWalker<DeadCodeElimination>> } } + void visitDrop(Drop* curr) { + if (isDead(curr->value)) { + replaceCurrent(curr->value); + } + } + void visitHost(Host* curr) { handleCall(curr); } diff --git a/src/passes/MergeBlocks.cpp b/src/passes/MergeBlocks.cpp index 494bf5a9e..37ada0174 100644 --- a/src/passes/MergeBlocks.cpp +++ b/src/passes/MergeBlocks.cpp @@ -169,6 +169,7 @@ static void optimizeBlock(Block* curr, Module* module) { // we can do it! // reuse the drop drop->value = child->list.back(); + drop->finalize(); child->list.back() = drop; child->finalize(); curr->list[i] = child; @@ -223,10 +224,17 @@ struct MergeBlocks : public WalkerPass<PostWalker<MergeBlocks>> { if (auto* block = child->dynCast<Block>()) { if (!block->name.is() && block->list.size() >= 2) { child = block->list.back(); + // we modified child )which is *&), which modifies curr, which might change its type + // (e.g. (drop (block i32 .. (unreachable))) + // the child was a block of i32, and is being replaced with an unreachable, so the + // parent will likely need to be unreachable too + auto oldType = curr->type; + ReFinalize().walk(curr); if (outer == nullptr) { // reuse the block, move it out block->list.back() = curr; - block->finalize(); // last block element was our input, and is now our output, which may differ TODO optimize + // we want the block outside to have the same type as curr had + block->finalize(oldType); replaceCurrent(block); return block; } else { @@ -264,7 +272,16 @@ struct MergeBlocks : public WalkerPass<PostWalker<MergeBlocks>> { } void visitSelect(Select* curr) { - optimize(curr, curr->condition, optimize(curr, curr->ifFalse, optimize(curr, curr->ifTrue), &curr->ifTrue), &curr->ifTrue, &curr->ifFalse); + Block* outer = nullptr; + outer = optimize(curr, curr->ifTrue, outer); + if (EffectAnalyzer(getPassOptions(), curr->ifTrue).hasSideEffects()) return; + outer = optimize(curr, curr->ifFalse, outer); + if (EffectAnalyzer(getPassOptions(), curr->ifFalse).hasSideEffects()) return; + /* . */ optimize(curr, curr->condition, outer); + } + + void visitDrop(Drop* curr) { + optimize(curr, curr->value); } void visitBreak(Break* curr) { diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 50a7c5589..0e7c34564 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -27,6 +27,7 @@ #include <ast_utils.h> #include <ast/cost.h> #include <ast/properties.h> +#include <ast/manipulation.h> namespace wasm { diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 37686de1a..e06103c11 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -23,6 +23,7 @@ #include <wasm-builder.h> #include <wasm-interpreter.h> #include <ast_utils.h> +#include "ast/manipulation.h" namespace wasm { @@ -142,6 +143,11 @@ struct Precompute : public WalkerPass<PostWalker<Precompute, UnifiedExpressionVi ExpressionManipulator::nop(curr); } } + + void visitFunction(Function* curr) { + // removing breaks can alter types + ReFinalize().walkFunctionInModule(curr, getModule()); + } }; Pass *createPrecomputePass() { diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 9a2edcb6b..a90cba7a7 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -25,6 +25,13 @@ namespace wasm { +static int forceFull() { + if (getenv("BINARYEN_PRINT_FULL")) { + return std::stoi(getenv("BINARYEN_PRINT_FULL")); + } + return 0; +} + struct PrintSExpression : public Visitor<PrintSExpression> { std::ostream& o; unsigned indent = 0; @@ -41,9 +48,7 @@ struct PrintSExpression : public Visitor<PrintSExpression> { PrintSExpression(std::ostream& o) : o(o) { setMinify(false); - if (getenv("BINARYEN_PRINT_FULL")) { - full = std::stoi(getenv("BINARYEN_PRINT_FULL")); - } + if (!full) full = forceFull(); } void visit(Expression* curr) { @@ -802,7 +807,7 @@ std::ostream& WasmPrinter::printExpression(Expression* expression, std::ostream& } PrintSExpression print(o); print.setMinify(minify); - if (full) { + if (full || forceFull()) { print.setFull(true); o << "[" << printWasmType(expression->type) << "] "; } diff --git a/src/passes/RelooperJumpThreading.cpp b/src/passes/RelooperJumpThreading.cpp index d3fd1844f..3656b5523 100644 --- a/src/passes/RelooperJumpThreading.cpp +++ b/src/passes/RelooperJumpThreading.cpp @@ -22,9 +22,11 @@ #include "wasm.h" #include "pass.h" #include "ast_utils.h" +#include "ast/manipulation.h" namespace wasm { + static Name LABEL("label"); static Name getInnerName(int i) { @@ -147,6 +149,11 @@ struct RelooperJumpThreading : public WalkerPass<ExpressionStackWalker<RelooperJ } } + void visitFunction(Function* curr) { + // we may alter types + ReFinalize().walkFunctionInModule(curr, getModule()); + } + private: bool hasIrreducibleControlFlow(If* iff, Expression* origin) { diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 785b1c92e..4273b9f63 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -305,19 +305,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { if (worked) { // Our work may alter block and if types, they may now return values that we made flow through them - struct TypeUpdater : public WalkerPass<PostWalker<TypeUpdater>> { - void visitBlock(Block* curr) { - curr->finalize(); - } - void visitLoop(Loop* curr) { - curr->finalize(); - } - void visitIf(If* curr) { - curr->finalize(); - } - }; - TypeUpdater typeUpdater; - typeUpdater.walkFunction(func); + ReFinalize().walkFunctionInModule(func, getModule()); } // thread trivial jumps @@ -370,18 +358,22 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { } } - void finish() { + void finish(Function* func) { for (auto& iter : newNames) { auto* br = iter.first; auto name = iter.second; br->name = name; } + if (newNames.size() > 0) { + // by changing where brs go, we may change block types etc. + ReFinalize().walkFunctionInModule(func, getModule()); + } } }; JumpThreader jumpThreader; jumpThreader.setModule(getModule()); jumpThreader.walkFunction(func); - jumpThreader.finish(); + jumpThreader.finish(func); // perform some final optimizations struct FinalOptimizer : public PostWalker<FinalOptimizer> { @@ -461,6 +453,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { )); curr->name = Name(); ExpressionManipulator::nop(br); + curr->finalize(curr->type); return; } } diff --git a/src/passes/RemoveUnusedNames.cpp b/src/passes/RemoveUnusedNames.cpp index f2c1f8525..39bf068df 100644 --- a/src/passes/RemoveUnusedNames.cpp +++ b/src/passes/RemoveUnusedNames.cpp @@ -72,6 +72,7 @@ struct RemoveUnusedNames : public WalkerPass<PostWalker<RemoveUnusedNames>> { WASM_UNREACHABLE(); } } + child->finalize(child->type); replaceCurrent(child); } } diff --git a/src/passes/SimplifyLocals.cpp b/src/passes/SimplifyLocals.cpp index 6609cfe71..a9e9de34b 100644 --- a/src/passes/SimplifyLocals.cpp +++ b/src/passes/SimplifyLocals.cpp @@ -47,6 +47,7 @@ #include <pass.h> #include <ast_utils.h> #include <ast/count.h> +#include <ast/manipulation.h> namespace wasm { diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index 13b9c62fd..1f820ec9d 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -22,14 +22,17 @@ #include <pass.h> #include <ast_utils.h> #include <wasm-builder.h> +#include <ast/block-utils.h> namespace wasm { -struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> { +struct Vacuum : public WalkerPass<PostWalker<Vacuum>> { bool isFunctionParallel() override { return true; } Pass* create() override { return new Vacuum; } + bool needRefinalize = false; + // returns nullptr if curr is dead, curr if it must stay as is, or another node if it can be replaced Expression* optimize(Expression* curr, bool resultUsed) { while (1) { @@ -164,46 +167,45 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> { list.push_back(last); } needResize = false; + needRefinalize = true; break; } } } if (needResize) { list.resize(size - skip); + // resizing means we drop elements, which may include breaks, which may + // render blocks unreachable now + needRefinalize = true; } - if (!curr->name.is()) { - if (list.size() == 1) { - // just one element. replace the block, either with it or with a nop if it's not needed - if (isConcreteWasmType(curr->type) || EffectAnalyzer(getPassOptions(), list[0]).hasSideEffects()) { - replaceCurrent(list[0]); - } else { - if (curr->type == unreachable) { - ExpressionManipulator::convert<Block, Unreachable>(curr); - } else { - ExpressionManipulator::nop(curr); - } - } - } else if (list.size() == 0) { - ExpressionManipulator::nop(curr); - } - } + // the block may now be a trivial one that we can get rid of and just leave its contents + replaceCurrent(BlockUtils::simplifyToContents(curr, this)); } void visitIf(If* curr) { // if the condition is a constant, just apply it // we can just return the ifTrue or ifFalse. if (auto* value = curr->condition->dynCast<Const>()) { + Expression* child; if (value->value.getInteger()) { - replaceCurrent(curr->ifTrue); - return; + child = curr->ifTrue; } else { if (curr->ifFalse) { - replaceCurrent(curr->ifFalse); + child = curr->ifFalse; } else { ExpressionManipulator::nop(curr); + return; } - return; } + replaceCurrent(child); + if (curr->type != child->type) { + // e.g., if (1) unreachable is none => unreachable + // or if i32 (1) unreachable else 10 is i32 => unreachable + // in which cases we must update our parents. + // we must do this now, so that our parents see valid data + ReFinalize().walk(getFunction()->body); + } + return; } if (curr->ifFalse) { if (curr->ifFalse->is<Nop>()) { @@ -253,8 +255,10 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> { // if we are dropping a block's return value, we might be able to remove it entirely if (auto* block = curr->value->dynCast<Block>()) { auto* last = block->list.back(); - if (isConcreteWasmType(last->type)) { - assert(block->type == last->type); + // 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 (isConcreteWasmType(last->type) && block->type == last->type) { last = optimize(last, false); if (!last) { // we may be able to remove this, if there are no brs @@ -303,6 +307,9 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> { } void visitFunction(Function* curr) { + if (needRefinalize) { + ReFinalize().walk(curr->body); + } auto* optimized = optimize(curr->body, curr->result != none); if (optimized) { curr->body = optimized; diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 7b4a896eb..2fb0d5c3b 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -166,11 +166,7 @@ static void dumpWast(Name name, Module* wasm) { } void PassRunner::run() { - // BINARYEN_PASS_DEBUG is a convenient commandline way to log out the toplevel passes, their times, - // and validate between each pass. - // (we don't recurse pass debug into sub-passes, as it doesn't help anyhow and - // also is bad for e.g. printing which is a pass) - static const int passDebug = getenv("BINARYEN_PASS_DEBUG") ? atoi(getenv("BINARYEN_PASS_DEBUG")) : 0; + static const int passDebug = getPassDebug(); if (!isNested && (options.debug || passDebug)) { // for debug logging purposes, run each pass in full before running the other auto totalTime = std::chrono::duration<double>(0); @@ -212,7 +208,7 @@ void PassRunner::run() { if (passDebug >= 2) { std::cerr << "Last pass (" << pass->name << ") broke validation. Here is the module before: \n" << moduleBefore.str() << "\n"; } else { - std::cerr << "Last pass (" << pass->name << ") broke validation. Run with BINARYEN_PASS_DEBUG=2 in the env to see the earlier state (FIXME: this is broken, need to prevent recursion of the print pass\n"; + std::cerr << "Last pass (" << pass->name << ") broke validation. Run with BINARYEN_PASS_DEBUG=2 in the env to see the earlier state, or 3 to dump byn-* files for each pass\n"; } abort(); } @@ -300,4 +296,9 @@ void PassRunner::runPassOnFunction(Pass* pass, Function* func) { instance->runFunction(this, wasm, func); } +int PassRunner::getPassDebug() { + static const int passDebug = getenv("BINARYEN_PASS_DEBUG") ? atoi(getenv("BINARYEN_PASS_DEBUG")) : 0; + return passDebug; +} + } // namespace wasm diff --git a/src/s2wasm.h b/src/s2wasm.h index d0579a402..6ba4750ec 100644 --- a/src/s2wasm.h +++ b/src/s2wasm.h @@ -1185,6 +1185,7 @@ class S2WasmBuilder { if (isConcreteWasmType(block->type) && block->list.size() == 0) { // empty blocks that return a value are not valid, fix that up block->list.push_back(allocator->alloc<Unreachable>()); + block->finalize(); } bstack.pop_back(); } else if (peek(".LBB")) { @@ -1207,7 +1208,7 @@ class S2WasmBuilder { } else if (match("end_loop")) { auto* loop = bstack.back()->cast<Loop>(); bstack.pop_back(); - loop->body->finalize(); + loop->body->cast<Block>()->finalize(); loop->finalize(loop->type); } else if (match("br_table")) { auto curr = allocator->alloc<Switch>(); @@ -1293,7 +1294,7 @@ class S2WasmBuilder { bstack.pop_back(); // remove the base block for the function body assert(bstack.empty()); assert(estack.empty()); - func->body->dynCast<Block>()->finalize(); + func->body->cast<Block>()->finalize(); wasm->addFunction(func); } diff --git a/src/tools/asm2wasm.cpp b/src/tools/asm2wasm.cpp index dd2b6a791..24cff6f09 100644 --- a/src/tools/asm2wasm.cpp +++ b/src/tools/asm2wasm.cpp @@ -24,6 +24,7 @@ #include "wasm-builder.h" #include "wasm-printing.h" #include "wasm-io.h" +#include "wasm-validator.h" #include "asm2wasm.h" @@ -189,7 +190,11 @@ int main(int argc, const char *argv[]) { } } - if (options.debug) std::cerr << "printing..." << std::endl; + if (!WasmValidator().validate(wasm)) { + Fatal() << "error in validating output"; + } + + if (options.debug) std::cerr << "emitting..." << std::endl; ModuleWriter writer; writer.setDebug(options.debug); writer.setDebugInfo(passOptions.debugInfo); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 04e0aa4de..42d3dfe47 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -17,7 +17,8 @@ #ifndef wasm_wasm_builder_h #define wasm_wasm_builder_h -#include <wasm.h> +#include "wasm.h" +#include "ast/manipulation.h" namespace wasm { @@ -379,6 +380,25 @@ public: std::swap(iff->ifTrue, iff->ifFalse); iff->condition = makeUnary(EqZInt32, iff->condition); } + + // returns a replacement with the precise same type, and with + // minimal contents. as a replacement, this may reuse the + // input node + template<typename T> + Expression* replaceWithIdenticalType(T* curr) { + Literal value; + // TODO: reuse node conditionally when possible for literals + switch (curr->type) { + case i32: value = Literal(int32_t(0)); break; + case i64: value = Literal(int64_t(0)); break; + case f32: value = Literal(float(0)); break; + case f64: value = Literal(double(0)); break; + case none: return ExpressionManipulator::nop(curr); + case unreachable: return ExpressionManipulator::convert<T, Unreachable>(curr); + } + return makeConst(value); + } + }; } // namespace wasm diff --git a/src/wasm-module-building.h b/src/wasm-module-building.h index 023a2d020..b501a1ab6 100644 --- a/src/wasm-module-building.h +++ b/src/wasm-module-building.h @@ -94,6 +94,7 @@ public: : wasm(wasm), numFunctions(numFunctions), passOptions(passOptions), addPrePasses(addPrePasses), endMarker(nullptr), list(nullptr), nextFunction(0), numWorkers(0), liveWorkers(0), activeWorkers(0), availableFuncs(0), finishedFuncs(0), finishing(false), debug(debug), validateGlobally(validateGlobally) { + if (!useWorkers()) { // if we shouldn't use threads, don't return; @@ -136,7 +137,7 @@ public: } bool useWorkers() { - return numFunctions > 0 && !debug && ThreadPool::getNumCores() > 1; + return numFunctions > 0 && !debug && ThreadPool::getNumCores() > 1 && !PassRunner::getPassDebug(); } // Add a function to the module, and to be optimized diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h index 522f5092c..f49407cad 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -157,7 +157,11 @@ struct Walker : public VisitorType { // just one visit*() method is called by the traversal; if you replace a node, // and you want to process the output, you must do that explicitly). Expression* replaceCurrent(Expression* expression) { - return replace = expression; + return *replacep = expression; + } + + Expression* getCurrent() { + return *replacep; } // Get the current module @@ -184,6 +188,15 @@ struct Walker : public VisitorType { setFunction(nullptr); } + void walkFunctionInModule(Function* func, Module* module) { + setModule(module); + setFunction(func); + static_cast<SubType*>(this)->doWalkFunction(func); + static_cast<SubType*>(this)->visitFunction(func); + setFunction(nullptr); + setModule(nullptr); + } + // override this to provide custom functionality void doWalkFunction(Function* func) { walk(func->body); @@ -264,12 +277,9 @@ struct Walker : public VisitorType { pushTask(SubType::scan, &root); while (stack.size() > 0) { auto task = popTask(); + replacep = task.currp; assert(*task.currp); task.func(static_cast<SubType*>(this), task.currp); - if (replace) { - *task.currp = replace; - replace = nullptr; - } } } @@ -311,7 +321,7 @@ struct Walker : public VisitorType { } private: - Expression* replace = nullptr; // a node to replace + Expression** replacep = nullptr; // the address of the current node, used to replace it std::vector<Task> stack; // stack of tasks Function* currFunction = nullptr; // current function being processed Module* currModule = nullptr; // current module being processed @@ -571,6 +581,13 @@ struct ExpressionStackWalker : public PostWalker<SubType, VisitorType> { self->pushTask(SubType::doPreVisit, currp); } + + Expression* replaceCurrent(Expression* expression) { + PostWalker<SubType, VisitorType>::replaceCurrent(expression); + // also update the stack + expressionStack.back() = expression; + return expression; + } }; // Traversal in the order of execution. This is quick and simple, but diff --git a/src/wasm-validator.h b/src/wasm-validator.h index b657b67a6..66375790c 100644 --- a/src/wasm-validator.h +++ b/src/wasm-validator.h @@ -42,6 +42,7 @@ #include "support/colors.h" #include "wasm.h" #include "wasm-printing.h" +#include "ast_utils.h" namespace wasm { @@ -68,7 +69,7 @@ struct WasmValidator : public PostWalker<WasmValidator> { void noteLabelName(Name name) { if (!name.is()) return; - shouldBeTrue(labelNames.find(name) == labelNames.end(), name, "names in Binaren IR must be unique - IR generators must ensure that"); + shouldBeTrue(labelNames.find(name) == labelNames.end(), name, "names in Binaryen IR must be unique - IR generators must ensure that"); labelNames.insert(name); } @@ -76,7 +77,13 @@ public: bool validate(Module& module, bool validateWeb_ = false, bool validateGlobally_ = true) { validateWeb = validateWeb_; validateGlobally = validateGlobally_; + // wasm logic validation walkModule(&module); + // validate additional internal IR details when in pass-debug mode + if (PassRunner::getPassDebug()) { + validateBinaryenIR(module); + } + // print if an error occurred if (!valid) { WasmPrinter::printModule(&module, std::cerr); } @@ -222,16 +229,23 @@ public: } } void visitBreak(Break *curr) { - noteBreak(curr->name, curr->value, curr); + // note breaks (that are actually taken) + if ((!curr->value || curr->value->type != unreachable) && + (!curr->condition || curr->condition->type != unreachable)) { + noteBreak(curr->name, curr->value, curr); + } if (curr->condition) { shouldBeTrue(curr->condition->type == unreachable || curr->condition->type == i32, curr, "break condition must be i32"); } } void visitSwitch(Switch *curr) { - for (auto& target : curr->targets) { - noteBreak(target, curr->value, curr); + // note breaks (that are actually taken) + if (curr->condition->type != unreachable && (!curr->value || curr->value->type != unreachable)) { + for (auto& target : curr->targets) { + noteBreak(target, curr->value, curr); + } + noteBreak(curr->default_, curr->value, curr); } - noteBreak(curr->default_, curr->value, curr); shouldBeTrue(curr->condition->type == unreachable || curr->condition->type == i32, curr, "br_table condition must be i32"); } void visitCall(Call *curr) { @@ -612,8 +626,6 @@ public: PostWalker<WasmValidator>::doWalkFunction(func); } -private: - // helpers std::ostream& fail() { @@ -718,6 +730,37 @@ private: default: {} } } + + void validateBinaryenIR(Module& wasm) { + struct BinaryenIRValidator : public PostWalker<BinaryenIRValidator, UnifiedExpressionVisitor<BinaryenIRValidator>> { + WasmValidator& parent; + + BinaryenIRValidator(WasmValidator& parent) : parent(parent) {} + + void visitExpression(Expression* curr) { + // check if a node type is 'stale', i.e., we forgot to finalize() the node. + auto oldType = curr->type; + ReFinalizeNode().visit(curr); + auto newType = curr->type; + if (newType != oldType) { + // We accept concrete => undefined, + // e.g. + // + // (drop (block i32 (unreachable))) + // + // 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 (!(isConcreteWasmType(oldType) && newType == unreachable)) { + parent.fail() << "stale type found in " << getFunction()->name << " on " << curr << "\n(marked as " << printWasmType(oldType) << ", should be " << printWasmType(newType) << ")\n"; + parent.valid = false; + } + curr->type = oldType; + } + } + }; + BinaryenIRValidator binaryenIRValidator(*this); + binaryenIRValidator.walkModule(&wasm); + } }; } // namespace wasm diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index f9e3912f7..9b053d5b8 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -185,9 +185,11 @@ Element* SExpressionParser::parseString() { input++; std::string str; while (1) { + if (input[0] == 0) throw ParseException("unterminated string", line, start - lineStart); if (input[0] == '"') break; if (input[0] == '\\') { str += input[0]; + if (input[1] == 0) throw ParseException("unterminated string escape", line, start - lineStart); str += input[1]; input += 2; continue; diff --git a/test/dot_s/unreachable_blocks.wast b/test/dot_s/unreachable_blocks.wast index f1593a00c..d9e921a4a 100644 --- a/test/dot_s/unreachable_blocks.wast +++ b/test/dot_s/unreachable_blocks.wast @@ -14,7 +14,7 @@ (return (i32.const 2) ) - (block $label$0 i32 + (block $label$0 (unreachable) ) ) @@ -22,7 +22,7 @@ (return (i64.const 3) ) - (block $label$0 i64 + (block $label$0 (unreachable) ) ) @@ -30,7 +30,7 @@ (return (f32.const 4.5) ) - (block $label$0 f32 + (block $label$0 (unreachable) ) ) @@ -38,7 +38,7 @@ (return (f64.const 5.5) ) - (block $label$0 f64 + (block $label$0 (unreachable) ) ) diff --git a/test/passes/O.txt b/test/passes/O.txt index eac56294b..d560e326b 100644 --- a/test/passes/O.txt +++ b/test/passes/O.txt @@ -1,7 +1,9 @@ (module (type $0 (func (result i32))) + (type $1 (func (param i64))) (memory $0 0) (export "ret" (func $ret)) + (export "waka" (func $if-0-unreachable-to-none)) (func $ret (type $0) (result i32) (block $out i32 (drop @@ -16,4 +18,7 @@ (i32.const 999) ) ) + (func $if-0-unreachable-to-none (type $1) (param $0 i64) + (unreachable) + ) ) diff --git a/test/passes/O.wast b/test/passes/O.wast index 8477c5306..8029c7b5b 100644 --- a/test/passes/O.wast +++ b/test/passes/O.wast @@ -13,5 +13,16 @@ (unreachable) ) ) + (func $if-0-unreachable-to-none (export "waka") (param $var$0 i64) + (local $var$1 i64) + (local $var$2 i64) + (block $label$1 + (if + (i32.const 0) + (br $label$1) + (unreachable) + ) + ) + ) ) diff --git a/test/passes/coalesce-locals-learning.txt b/test/passes/coalesce-locals-learning.txt index 08cb9a004..3bd04680d 100644 --- a/test/passes/coalesce-locals-learning.txt +++ b/test/passes/coalesce-locals-learning.txt @@ -110,7 +110,7 @@ (br $block) (nop) (drop - (unreachable) + (i32.const 0) ) (nop) ) @@ -391,10 +391,10 @@ (block $block (br $block) (drop - (unreachable) + (i32.const 0) ) (drop - (unreachable) + (i32.const 0) ) ) ) @@ -403,10 +403,10 @@ (block $block (unreachable) (drop - (unreachable) + (i32.const 0) ) (drop - (unreachable) + (i32.const 0) ) ) ) @@ -415,10 +415,10 @@ (block $block (return) (drop - (unreachable) + (i32.const 0) ) (drop - (unreachable) + (i32.const 0) ) ) ) @@ -462,7 +462,7 @@ (i32.const 100) ) (drop - (unreachable) + (i32.const 0) ) ) (drop diff --git a/test/passes/coalesce-locals.txt b/test/passes/coalesce-locals.txt index e022d59f9..381c76653 100644 --- a/test/passes/coalesce-locals.txt +++ b/test/passes/coalesce-locals.txt @@ -114,7 +114,7 @@ (br $block) (nop) (drop - (unreachable) + (i32.const 0) ) (nop) ) @@ -393,10 +393,10 @@ (block $block (br $block) (drop - (unreachable) + (i32.const 0) ) (drop - (unreachable) + (i32.const 0) ) ) ) @@ -405,10 +405,10 @@ (block $block (unreachable) (drop - (unreachable) + (i32.const 0) ) (drop - (unreachable) + (i32.const 0) ) ) ) @@ -417,10 +417,10 @@ (block $block (return) (drop - (unreachable) + (i32.const 0) ) (drop - (unreachable) + (i32.const 0) ) ) ) @@ -464,7 +464,7 @@ (i32.const 100) ) (drop - (unreachable) + (i32.const 0) ) ) (drop @@ -904,7 +904,7 @@ (return) (nop) (drop - (unreachable) + (i32.const 0) ) (nop) ) @@ -912,7 +912,7 @@ (unreachable) (nop) (drop - (unreachable) + (i32.const 0) ) (nop) ) @@ -920,7 +920,7 @@ (br $z) (nop) (drop - (unreachable) + (i32.const 0) ) (nop) ) @@ -930,7 +930,7 @@ ) (nop) (drop - (unreachable) + (i32.const 0) ) (nop) ) @@ -940,8 +940,8 @@ (block $block (unreachable) (i32.store - (unreachable) - (unreachable) + (i32.const 0) + (i32.const 0) ) ) ) @@ -1028,7 +1028,7 @@ (loop $top (if (i32.const 1) - (set_local $0 + (tee_local $0 (unreachable) ) ) diff --git a/test/passes/dce.txt b/test/passes/dce.txt index 3ccdbdffd..62cee370f 100644 --- a/test/passes/dce.txt +++ b/test/passes/dce.txt @@ -47,21 +47,15 @@ ) (if (i32.const 0) - (drop - (unreachable) - ) + (unreachable) ) (if (i32.const 0) - (drop - (unreachable) - ) + (unreachable) ) (if (i32.const 0) - (drop - (unreachable) - ) + (unreachable) ) (block $out16 (block $in @@ -199,9 +193,7 @@ ) (if (i32.const 22) - (drop - (unreachable) - ) + (unreachable) ) (if (i32.const 33) @@ -222,25 +214,19 @@ ) (if (i32.const 66) - (drop - (unreachable) - ) + (unreachable) ) (if (i32.const 77) - (drop - (unreachable) - ) + (unreachable) ) (if (i32.const 88) - (drop - (block - (drop - (i32.const 0) - ) - (unreachable) + (block + (drop + (i32.const 0) ) + (unreachable) ) ) (if @@ -249,34 +235,28 @@ ) (if (i32.const 100) - (drop - (block - (drop - (i32.const 123) - ) - (drop - (i32.const 456) - ) - (unreachable) + (block + (drop + (i32.const 123) ) + (drop + (i32.const 456) + ) + (unreachable) ) ) (if (i32.const 101) - (drop - (block - (drop - (i32.const 123) - ) - (unreachable) + (block + (drop + (i32.const 123) ) + (unreachable) ) ) (if (i32.const 102) - (drop - (unreachable) - ) + (unreachable) ) (drop (i32.const 1337) @@ -313,7 +293,6 @@ (get_local $$$0) ) ) - (unreachable) ) (func $global (type $1) (unreachable) @@ -336,20 +315,14 @@ ) ) (func $unreachable-block-ends-switch (type $2) (result i32) - (block $label$0 i32 - (block $label$3 - (nop) - (unreachable) - ) + (block $label$3 + (nop) (unreachable) ) ) (func $unreachable-block-ends-br_if (type $1) (result i32) - (block $label$0 i32 - (block $label$2 - (nop) - (unreachable) - ) + (block $label$2 + (nop) (unreachable) ) ) @@ -364,13 +337,11 @@ (drop (i32.const 1) ) - (drop - (block - (drop - (i32.const 4104) - ) - (unreachable) + (block + (drop + (i32.const 4104) ) + (unreachable) ) ) (func $call-unreach (type $4) (param $var$0 i64) (param $var$1 i64) (result i64) @@ -404,4 +375,70 @@ ) ) ) + (func $br-gone-means-block-type-changes-then-refinalize-at-end-is-too-late (type $1) (param $var$0 i32) (result i32) + (block $block + (nop) + (unreachable) + ) + ) + (func $br-with-unreachable-value-should-not-give-a-block-a-value (type $1) (param $var$0 i32) (result i32) + (block $label$0 i32 + (block $block + (drop + (br_if $label$0 + (i32.const 8) + (get_local $var$0) + ) + ) + (unreachable) + ) + ) + ) + (func $replace-br-value-of-i32-with-unreachable (type $2) (result i32) + (block $label$1 + (nop) + (unreachable) + ) + ) + (func $shorten-block-requires-sync-refinalize (type $ii) (param $var$0 i32) (param $var$1 i32) + (unreachable) + ) + (func $block-with-type-but-is-unreachable (type $3) (param $var$0 i32) (result i32) + (block $block + (nop) + (unreachable) + ) + ) + (func $if-with-type-but-is-unreachable (type $3) (param $var$0 i32) (result i32) + (if + (get_local $var$0) + (unreachable) + (unreachable) + ) + ) + (func $unreachable-loop (type $1) + (unreachable) + ) + (func $br-block-from-unary (type $2) (result i32) + (block $label$6 i32 + (br $label$6 + (i32.const 8) + ) + ) + ) + (func $replace-unary-with-br-child (type $1) + (drop + (block $label$6 i32 + (br $label$6 + (i32.const 8) + ) + ) + ) + ) + (func $br_if-unreach-then-br_if-normal (type $1) + (block $out + (nop) + (unreachable) + ) + ) ) diff --git a/test/passes/dce.wast b/test/passes/dce.wast index 87a5757cd..7ee37e874 100644 --- a/test/passes/dce.wast +++ b/test/passes/dce.wast @@ -538,4 +538,117 @@ ) ) ) + (func $br-gone-means-block-type-changes-then-refinalize-at-end-is-too-late (type $1) (param $var$0 i32) (result i32) + (block $label$0 i32 + (br $label$0 + (block i32 + (nop) + (drop + (br_if $label$0 + (unreachable) + (get_local $var$0) + ) + ) + (i32.const 4) + ) + ) + ) + ) + (func $br-with-unreachable-value-should-not-give-a-block-a-value (type $1) (param $var$0 i32) (result i32) + (block $label$0 i32 + (br $label$0 + (block i32 ;; turns into unreachable when refinalized + (drop + (br_if $label$0 + (i32.const 8) + (get_local $var$0) + ) + ) + (unreachable) + ) + ) + (i32.const 16) + ) + ) + (func $replace-br-value-of-i32-with-unreachable (result i32) + (block $label$0 i32 + (br $label$0 + (block $label$1 i32 + (nop) + (unreachable) + ) + ) + ) + ) + (func $shorten-block-requires-sync-refinalize (param $var$0 i32) (param $var$1 i32) + (block $label$0 + (unreachable) + (if + (unreachable) + (br_if $label$0 + (get_local $var$1) + ) + ) + ) + ) + (func $block-with-type-but-is-unreachable (param $var$0 i32) (result i32) + (block $label$0 i32 + (br $label$0 + (block $block i32 + (nop) + (unreachable) + ) + ) + ) + ) + (func $if-with-type-but-is-unreachable (param $var$0 i32) (result i32) + (block $label$0 i32 + (br $label$0 + (if i32 + (get_local $var$0) + (unreachable) + (unreachable) + ) + ) + ) + ) + (func $unreachable-loop + (loop $label$2 + (unreachable) + (br $label$2) + ) + ) + (func $br-block-from-unary (result i32) + (block $label$6 i32 + (i32.ctz + (block $label$7 i32 + (br $label$6 + (i32.const 8) + ) + ) + ) + ) + ) + (func $replace-unary-with-br-child + (drop + (block $label$6 i32 + (i32.ctz + (br $label$6 + (i32.const 8) + ) + ) + ) + ) + ) + (func $br_if-unreach-then-br_if-normal + (block $out + (nop) + (br_if $out + (unreachable) + ) + (br_if $out + (i32.const 1) + ) + ) + ) ) diff --git a/test/passes/dce_vacuum.bin.txt b/test/passes/dce_vacuum.bin.txt index 8d365b327..dc8138b24 100644 --- a/test/passes/dce_vacuum.bin.txt +++ b/test/passes/dce_vacuum.bin.txt @@ -5,7 +5,7 @@ (export "f32.compute_radix" (func $0)) (export "f64.compute_radix" (func $1)) (func $0 (type $0) (param $var$0 f32) (param $var$1 f32) (result f32) - (block $label$0 f32 + (block $label$0 (loop $label$1 (br_if $label$1 (f32.eq @@ -28,24 +28,22 @@ ) ) ) - (drop - (block - (drop - (call $0 - (f32.add - (get_local $var$0) - (tee_local $var$1 - (f32.add - (get_local $var$1) - (f32.const 1) - ) + (block + (drop + (call $0 + (f32.add + (get_local $var$0) + (tee_local $var$1 + (f32.add + (get_local $var$1) + (f32.const 1) ) ) - (get_local $var$0) ) + (get_local $var$0) ) - (unreachable) ) + (unreachable) ) ) ) diff --git a/test/passes/dce_vacuum.txt b/test/passes/dce_vacuum.txt index 0f1ec5095..ac61cce04 100644 --- a/test/passes/dce_vacuum.txt +++ b/test/passes/dce_vacuum.txt @@ -9,9 +9,7 @@ ) ) (func $drop-unreachable (type $1) (param $var$0 f32) (param $var$1 f32) (result f32) - (drop - (unreachable) - ) + (unreachable) ) (func $set-unreachable (type $2) (param $var$0 i64) (result i64) (local $var$1 i64) diff --git a/test/passes/precompute.txt b/test/passes/precompute.txt index acd96dee5..3de84aabe 100644 --- a/test/passes/precompute.txt +++ b/test/passes/precompute.txt @@ -105,4 +105,28 @@ (return) ) ) + (func $refinalize-br-condition-unreachable (type $2) + (block $label$1 + (drop + (br_if $label$1 + (unreachable) + ) + ) + ) + ) + (func $br_if-condition-is-block-i32-but-unreachable-so-refinalize-tricky (type $2) + (drop + (block $label$1 + (drop + (br_if $label$1 + (i32.const 100) + (block $label$3 + (unreachable) + ) + ) + ) + (i32.const 0) + ) + ) + ) ) diff --git a/test/passes/precompute.wast b/test/passes/precompute.wast index d5e91fb9d..aafa8e947 100644 --- a/test/passes/precompute.wast +++ b/test/passes/precompute.wast @@ -186,4 +186,28 @@ (return) ) ) + (func $refinalize-br-condition-unreachable + (block $label$1 + (drop + (br_if $label$1 + (unreachable) + ) + ) + ) + ) + (func $br_if-condition-is-block-i32-but-unreachable-so-refinalize-tricky + (drop + (block $label$1 i32 + (drop + (br_if $label$1 + (i32.const 100) + (block $label$3 i32 + (unreachable) + ) + ) + ) + (i32.const 0) + ) + ) + ) ) diff --git a/test/passes/precompute_coalesce-locals_vacuum.txt b/test/passes/precompute_coalesce-locals_vacuum.txt index 76b2303f2..fcf3cdf5d 100644 --- a/test/passes/precompute_coalesce-locals_vacuum.txt +++ b/test/passes/precompute_coalesce-locals_vacuum.txt @@ -2,9 +2,10 @@ (type $0 (func (param i32) (result i32))) (memory $0 0) (func $nested-br_if-value (type $0) (param $0 i32) (result i32) - (loop $label$0 i32 - (block $block i32 + (loop $label$0 + (block $block (br $label$0) + (i32.const 0) ) ) ) diff --git a/test/passes/remove-unused-names_merge-blocks.txt b/test/passes/remove-unused-names_merge-blocks.txt index 50f34c7b2..3c4d7a0a0 100644 --- a/test/passes/remove-unused-names_merge-blocks.txt +++ b/test/passes/remove-unused-names_merge-blocks.txt @@ -448,33 +448,27 @@ (drop (i32.const 10) ) + (unreachable) + (drop + (i32.const 50) + ) (drop (select (i32.const 20) - (block i32 - (unreachable) - (i32.const 40) - ) - (block i32 - (drop - (i32.const 50) - ) - (i32.const 60) - ) + (i32.const 40) + (i32.const 60) ) ) (drop (i32.const 10) ) (drop + (i32.const 30) + ) + (drop (select (i32.const 20) - (block i32 - (drop - (i32.const 30) - ) - (unreachable) - ) + (unreachable) (block i32 (drop (i32.const 50) @@ -489,14 +483,12 @@ (drop (i32.const 30) ) + (unreachable) (drop (select (i32.const 20) (i32.const 40) - (block i32 - (unreachable) - (i32.const 60) - ) + (i32.const 60) ) ) (drop @@ -506,15 +498,13 @@ (i32.const 30) ) (drop + (i32.const 50) + ) + (drop (select (i32.const 20) (i32.const 40) - (block i32 - (drop - (i32.const 50) - ) - (unreachable) - ) + (unreachable) ) ) ) @@ -751,4 +741,11 @@ ) (unreachable) ) + (func $drop-unreachable (type $4) (result i32) + (local $0 i32) + (drop + (unreachable) + ) + (unreachable) + ) ) diff --git a/test/passes/remove-unused-names_merge-blocks.wast b/test/passes/remove-unused-names_merge-blocks.wast index d41ff5fa4..adb27e6f0 100644 --- a/test/passes/remove-unused-names_merge-blocks.wast +++ b/test/passes/remove-unused-names_merge-blocks.wast @@ -914,4 +914,16 @@ ) (unreachable) ) + + (func $drop-unreachable (result i32) + (local $0 i32) + (block $label$1 i32 + (drop + (block i32 + (unreachable) + ) + ) + (unreachable) + ) + ) ) diff --git a/test/passes/remove-unused-names_precompute.txt b/test/passes/remove-unused-names_precompute.txt new file mode 100644 index 000000000..dccd4708e --- /dev/null +++ b/test/passes/remove-unused-names_precompute.txt @@ -0,0 +1,25 @@ +(module + (type $0 (func (param i32))) + (memory $0 256 256) + (func $__ZN10WasmAssertC2Ev__async_cb (type $0) (param $$0 i32) + (block $switch-default + (nop) + (block + (i32.store + (i32.const 12) + (i32.const 26) + ) + (return) + ) + ) + (block + (set_local $$0 + (i32.const 4) + ) + (i32.store + (get_local $$0) + (i32.const 1) + ) + ) + ) +) diff --git a/test/passes/remove-unused-names_precompute.wast b/test/passes/remove-unused-names_precompute.wast new file mode 100644 index 000000000..5e1853389 --- /dev/null +++ b/test/passes/remove-unused-names_precompute.wast @@ -0,0 +1,28 @@ +(module + (memory $0 256 256) + (func $__ZN10WasmAssertC2Ev__async_cb (param $$0 i32) + (block $switch-default + (block $switch-case + (br_table $switch-case $switch-default + (i32.const 0) + ) + ) + (block + (i32.store + (i32.const 12) + (i32.const 26) + ) + (return) + ) + ) + (block + (set_local $$0 + (i32.const 4) + ) + (i32.store + (get_local $$0) + (i32.const 1) + ) + ) + ) +) diff --git a/test/passes/remove-unused-names_vacuum.txt b/test/passes/remove-unused-names_vacuum.txt index 05112bf48..a1f49128e 100644 --- a/test/passes/remove-unused-names_vacuum.txt +++ b/test/passes/remove-unused-names_vacuum.txt @@ -1,5 +1,6 @@ (module (type $0 (func (result i32))) + (type $1 (func)) (memory $0 0) (func $return-i32-but-body-is-unreachable3 (type $0) (result i32) (local $label i32) @@ -10,4 +11,11 @@ (unreachable) (i32.const 0) ) + (func $to-drop-unreachable (type $1) + (drop + (block i32 + (unreachable) + ) + ) + ) ) diff --git a/test/passes/remove-unused-names_vacuum.wast b/test/passes/remove-unused-names_vacuum.wast index 694d95cfd..cb5affac3 100644 --- a/test/passes/remove-unused-names_vacuum.wast +++ b/test/passes/remove-unused-names_vacuum.wast @@ -16,5 +16,12 @@ ) (i32.const 0) ) + (func $to-drop-unreachable + (drop + (block i32 + (unreachable) + ) + ) + ) ) diff --git a/test/passes/rereloop_dce_remove-unused-brs_remove-unused-names_simplify-locals-nostructure_vacuum_reorder-locals_coalesce-locals_simplify-locals_reorder-locals_merge-blocks_remove-unused-brs_merge-blocks_vacuum.txt b/test/passes/rereloop_dce_remove-unused-brs_remove-unused-names_simplify-locals-nostructure_vacuum_reorder-locals_coalesce-locals_simplify-locals_reorder-locals_merge-blocks_remove-unused-brs_merge-blocks_vacuum.txt index 5ed8e80c7..d41cd386e 100644 --- a/test/passes/rereloop_dce_remove-unused-brs_remove-unused-names_simplify-locals-nostructure_vacuum_reorder-locals_coalesce-locals_simplify-locals_reorder-locals_merge-blocks_remove-unused-brs_merge-blocks_vacuum.txt +++ b/test/passes/rereloop_dce_remove-unused-brs_remove-unused-names_simplify-locals-nostructure_vacuum_reorder-locals_coalesce-locals_simplify-locals_reorder-locals_merge-blocks_remove-unused-brs_merge-blocks_vacuum.txt @@ -46,14 +46,12 @@ (get_local $0) ) ) - (block $block$11$break - (loop $shape$9$continue - (call $trivial) - (br_if $shape$9$continue + (loop $shape$9$continue + (call $trivial) + (br_if $shape$9$continue + (i32.eqz (i32.eqz - (i32.eqz - (get_local $0) - ) + (get_local $0) ) ) ) diff --git a/test/passes/vacuum.txt b/test/passes/vacuum.txt index f64ef0868..be18cc713 100644 --- a/test/passes/vacuum.txt +++ b/test/passes/vacuum.txt @@ -17,19 +17,11 @@ (set_local $x (get_local $x) ) - (block $in-a-block - ) - (block $two-in-a-block - ) (set_local $x - (block $result-used i32 - (get_local $x) - ) + (get_local $x) ) (set_local $x - (block $two-and-result-used i32 - (get_local $y) - ) + (get_local $y) ) ) (func $loopy (type $1) (param $0 i32) @@ -98,20 +90,10 @@ ) ) (func $block-to-one (type $0) - (block $block0 - ) - (block $block1 - (unreachable) - ) - (block $block2 - (unreachable) - ) - (block $block3 - (unreachable) - ) - (block $block4 - (unreachable) - ) + (unreachable) + (unreachable) + (unreachable) + (unreachable) ) (func $recurse (type $0) (nop) @@ -123,20 +105,18 @@ (if (if i32 (get_local $d) - (block $block1 i32 - (f64.ne - (f64.promote/f32 - (f32.load - (tee_local $l - (i32.add - (get_local $b) - (i32.const 60) - ) + (f64.ne + (f64.promote/f32 + (f32.load + (tee_local $l + (i32.add + (get_local $b) + (i32.const 60) ) ) ) - (get_local $e) ) + (get_local $e) ) (i32.const 0) ) @@ -185,22 +165,7 @@ (func $relooperJumpThreading1 (type $0) (local $$vararg_ptr5 i32) (local $$11 i32) - (loop $while-in$1 - (drop - (block $jumpthreading$outer$8 i32 - (block $jumpthreading$inner$8 - (br $jumpthreading$outer$8 - (i32.const 0) - ) - ) - (i32.store - (get_local $$vararg_ptr5) - (get_local $$11) - ) - (i32.const 0) - ) - ) - ) + (nop) ) (func $relooperJumpThreading2 (type $0) (nop) @@ -259,4 +224,36 @@ ) ) ) + (func $if-1-block (type $1) (param $x i32) + (block $out + (if + (get_local $x) + (block $block9 + (set_local $x + (get_local $x) + ) + (br $out) + ) + ) + ) + ) + (func $block-resize-br-gone (type $0) + (block $out + (block $in + (call $block-resize-br-gone) + (br $in) + ) + (return) + ) + (block $out2 + (block $in2 + (br $in2) + ) + (return) + ) + ) + (func $block-unreachable-but-last-element-concrete (type $0) + (local $2 i32) + (nop) + ) ) diff --git a/test/passes/vacuum.wast b/test/passes/vacuum.wast index 8b97b9386..01c928e4d 100644 --- a/test/passes/vacuum.wast +++ b/test/passes/vacuum.wast @@ -463,4 +463,50 @@ ) ) ) + (func $if-1-block (param $x i32) + (block $out + (if + (get_local $x) + (block + (if + (i32.const 1) + (block + (set_local $x + (get_local $x) + ) + (br $out) + ) + ) + ) + ) + ) + ) + (func $block-resize-br-gone + (block $out + (block $in + (call $block-resize-br-gone) + (br $in) + (br $out) + ) + (return) + ) + (block $out2 + (block $in2 + (br $in2) + (br $out2) + ) + (return) + ) + ) + (func $block-unreachable-but-last-element-concrete + (local $2 i32) + (block $label$0 + (drop + (block + (br $label$0) + (get_local $2) + ) + ) + ) + ) ) diff --git a/test/unreachable-import_wasm-only.fromasm b/test/unreachable-import_wasm-only.fromasm index 642d266dc..fad6e4de8 100644 --- a/test/unreachable-import_wasm-only.fromasm +++ b/test/unreachable-import_wasm-only.fromasm @@ -8,19 +8,17 @@ (data (get_global $memoryBase) "unreachable-import_wasm-only.asm.js") (export "__ZN10WasmAssertC2Ev__async_cb" (func $__ZN10WasmAssertC2Ev__async_cb)) (func $__ZN10WasmAssertC2Ev__async_cb (param $0 i32) - (block $switch-default - (i32.store - (i32.const 12) - (i32.const 26) - ) - (return) + (i32.store + (i32.const 12) + (i32.const 26) ) + (return) (i32.store - (unreachable) + (i32.const 0) (i32.const 1) ) (call $___cxa_throw - (unreachable) + (i32.const 0) (i32.const 1280) (i32.const 0) ) diff --git a/test/unreachable-import_wasm-only.fromasm.clamp b/test/unreachable-import_wasm-only.fromasm.clamp index 642d266dc..fad6e4de8 100644 --- a/test/unreachable-import_wasm-only.fromasm.clamp +++ b/test/unreachable-import_wasm-only.fromasm.clamp @@ -8,19 +8,17 @@ (data (get_global $memoryBase) "unreachable-import_wasm-only.asm.js") (export "__ZN10WasmAssertC2Ev__async_cb" (func $__ZN10WasmAssertC2Ev__async_cb)) (func $__ZN10WasmAssertC2Ev__async_cb (param $0 i32) - (block $switch-default - (i32.store - (i32.const 12) - (i32.const 26) - ) - (return) + (i32.store + (i32.const 12) + (i32.const 26) ) + (return) (i32.store - (unreachable) + (i32.const 0) (i32.const 1) ) (call $___cxa_throw - (unreachable) + (i32.const 0) (i32.const 1280) (i32.const 0) ) diff --git a/test/unreachable-import_wasm-only.fromasm.imprecise b/test/unreachable-import_wasm-only.fromasm.imprecise index c6a954d24..a85dc9ed8 100644 --- a/test/unreachable-import_wasm-only.fromasm.imprecise +++ b/test/unreachable-import_wasm-only.fromasm.imprecise @@ -7,19 +7,17 @@ (import "env" "tableBase" (global $tableBase i32)) (export "__ZN10WasmAssertC2Ev__async_cb" (func $__ZN10WasmAssertC2Ev__async_cb)) (func $__ZN10WasmAssertC2Ev__async_cb (param $0 i32) - (block $switch-default - (i32.store - (i32.const 12) - (i32.const 26) - ) - (return) + (i32.store + (i32.const 12) + (i32.const 26) ) + (return) (i32.store - (unreachable) + (i32.const 0) (i32.const 1) ) (call $___cxa_throw - (unreachable) + (i32.const 0) (i32.const 1280) (i32.const 0) ) diff --git a/test/wasm-only.asm.js b/test/wasm-only.asm.js index 371197173..7287126d0 100644 --- a/test/wasm-only.asm.js +++ b/test/wasm-only.asm.js @@ -16,7 +16,10 @@ function asm(global, env, buffer) { var HEAPF32 = new global.Float32Array(buffer); var HEAPF64 = new global.Float64Array(buffer); + var STACKTOP = env.STACKTOP | 0; + var fround = global.Math.fround; + var Math_imul = global.Math.imul; var illegalImport = env.illegalImport; var illegalImportResult = env.illegalImportResult; @@ -284,6 +287,135 @@ function asm(global, env, buffer) { } return 44; } + function _memchr($src,$c,$n) { + $src = $src|0; + $c = $c|0; + $n = $n|0; + var $0 = 0, $1 = 0, $2 = 0, $3 = 0, $4 = 0, $5 = 0, $6 = 0, $7 = 0, $and = 0, $and15 = 0, $and16 = 0, $and39 = 0, $cmp = 0, $cmp11 = 0, $cmp1132 = 0, $cmp28 = 0, $cmp8 = 0, $cond = 0, $conv1 = 0, $dec = 0; + var $dec34 = 0, $incdec$ptr = 0, $incdec$ptr21 = 0, $incdec$ptr33 = 0, $lnot = 0, $mul = 0, $n$addr$0$lcssa = 0, $n$addr$0$lcssa52 = 0, $n$addr$043 = 0, $n$addr$1$lcssa = 0, $n$addr$133 = 0, $n$addr$227 = 0, $n$addr$3 = 0, $neg = 0, $or$cond = 0, $or$cond42 = 0, $s$0$lcssa = 0, $s$0$lcssa53 = 0, $s$044 = 0, $s$128 = 0; + var $s$2 = 0, $sub = 0, $sub22 = 0, $tobool = 0, $tobool2 = 0, $tobool2$lcssa = 0, $tobool241 = 0, $tobool25 = 0, $tobool2526 = 0, $tobool36 = 0, $tobool40 = 0, $w$0$lcssa = 0, $w$034 = 0, $xor = 0, label = 0, sp = 0; + sp = STACKTOP; + $conv1 = $c & 255; + $0 = $src; + $and39 = $0 & 3; + $tobool40 = ($and39|0)!=(0); + $tobool241 = ($n|0)!=(0); + $or$cond42 = $tobool241 & $tobool40; + L1: do { + if ($or$cond42) { + $1 = $c&255; + $n$addr$043 = $n;$s$044 = $src; + while(1) { + $2 = load1($s$044); + $cmp = ($2<<24>>24)==($1<<24>>24); + if ($cmp) { + $n$addr$0$lcssa52 = $n$addr$043;$s$0$lcssa53 = $s$044; + label = 6; + break L1; + } + $incdec$ptr = ((($s$044)) + 1|0); + $dec = (($n$addr$043) + -1)|0; + $3 = $incdec$ptr; + $and = $3 & 3; + $tobool = ($and|0)!=(0); + $tobool2 = ($dec|0)!=(0); + $or$cond = $tobool2 & $tobool; + if ($or$cond) { + $n$addr$043 = $dec;$s$044 = $incdec$ptr; + } else { + $n$addr$0$lcssa = $dec;$s$0$lcssa = $incdec$ptr;$tobool2$lcssa = $tobool2; + label = 5; + break; + } + } + } else { + $n$addr$0$lcssa = $n;$s$0$lcssa = $src;$tobool2$lcssa = $tobool241; + label = 5; + } + } while(0); + if ((label|0) == 5) { + if ($tobool2$lcssa) { + $n$addr$0$lcssa52 = $n$addr$0$lcssa;$s$0$lcssa53 = $s$0$lcssa; + label = 6; + } else { + $n$addr$3 = 0;$s$2 = $s$0$lcssa; + } + } + L8: do { + if ((label|0) == 6) { + $4 = load1($s$0$lcssa53); + $5 = $c&255; + $cmp8 = ($4<<24>>24)==($5<<24>>24); + if ($cmp8) { + $n$addr$3 = $n$addr$0$lcssa52;$s$2 = $s$0$lcssa53; + } else { + $mul = Math_imul($conv1, 16843009)|0; + $cmp1132 = ($n$addr$0$lcssa52>>>0)>(3); + L11: do { + if ($cmp1132) { + $n$addr$133 = $n$addr$0$lcssa52;$w$034 = $s$0$lcssa53; + while(1) { + $6 = load4($w$034); + $xor = $6 ^ $mul; + $sub = (($xor) + -16843009)|0; + $neg = $xor & -2139062144; + $and15 = $neg ^ -2139062144; + $and16 = $and15 & $sub; + $lnot = ($and16|0)==(0); + if (!($lnot)) { + break; + } + $incdec$ptr21 = ((($w$034)) + 4|0); + $sub22 = (($n$addr$133) + -4)|0; + $cmp11 = ($sub22>>>0)>(3); + if ($cmp11) { + $n$addr$133 = $sub22;$w$034 = $incdec$ptr21; + } else { + $n$addr$1$lcssa = $sub22;$w$0$lcssa = $incdec$ptr21; + label = 11; + break L11; + } + } + $n$addr$227 = $n$addr$133;$s$128 = $w$034; + } else { + $n$addr$1$lcssa = $n$addr$0$lcssa52;$w$0$lcssa = $s$0$lcssa53; + label = 11; + } + } while(0); + if ((label|0) == 11) { + $tobool2526 = ($n$addr$1$lcssa|0)==(0); + if ($tobool2526) { + $n$addr$3 = 0;$s$2 = $w$0$lcssa; + break; + } else { + $n$addr$227 = $n$addr$1$lcssa;$s$128 = $w$0$lcssa; + } + } + while(1) { + $7 = load1($s$128); + $cmp28 = ($7<<24>>24)==($5<<24>>24); + if ($cmp28) { + $n$addr$3 = $n$addr$227;$s$2 = $s$128; + break L8; + } + $incdec$ptr33 = ((($s$128)) + 1|0); + $dec34 = (($n$addr$227) + -1)|0; + $tobool25 = ($dec34|0)==(0); + if ($tobool25) { + $n$addr$3 = 0;$s$2 = $incdec$ptr33; + break; + } else { + $n$addr$227 = $dec34;$s$128 = $incdec$ptr33; + } + } + } + } + } while(0); + $tobool36 = ($n$addr$3|0)!=(0); + $cond = $tobool36 ? $s$2 : 0; + return ($cond|0); + } + function keepAlive() { loads(); stores(); @@ -297,6 +429,8 @@ function asm(global, env, buffer) { ifValue32(0, 0) | 0; switch64(i64(0)) | 0; unreachable_leftovers(0, 0, 0); + _memchr(0, 0, 0) | 0; + switch64TOOMUCH(i64(0)) | 0; } function __emscripten_dceable_type_decls() { // dce-able, but this defines the type of fabsf which has no other use diff --git a/test/wasm-only.fromasm b/test/wasm-only.fromasm index 6fa30ba12..3a98e84a3 100644 --- a/test/wasm-only.fromasm +++ b/test/wasm-only.fromasm @@ -365,6 +365,325 @@ ) ) ) + (func $switch64TOOMUCH (param $0 i64) (result i32) + (local $1 i32) + (local $2 i64) + (block $switch-default + (if + (i64.ne + (tee_local $2 + (get_local $0) + ) + (i64.const -9223372036854775808) + ) + (br_if $switch-default + (i64.ne + (get_local $2) + (i64.const 4611686018427387904) + ) + ) + ) + (return + (i32.const 40) + ) + ) + (block $switch-default4 + (if + (i32.ne + (tee_local $1 + (i32.const 100) + ) + (i32.const 214748364) + ) + (br_if $switch-default4 + (i32.ne + (get_local $1) + (i32.const 107374182) + ) + ) + ) + (return + (i32.const 41) + ) + ) + (block $switch5 + (if + (i64.ne + (get_local $0) + (i64.const -9223372036854775808) + ) + (br_if $switch5 + (i64.ne + (get_local $0) + (i64.const 4611686018427387904) + ) + ) + ) + (return + (i32.const 42) + ) + ) + (block $switch8 + (if + (i32.ne + (tee_local $1 + (i32.const 100) + ) + (i32.const 214748364) + ) + (br_if $switch8 + (i32.ne + (get_local $1) + (i32.const 107374182) + ) + ) + ) + (return + (i32.const 43) + ) + ) + (i32.const 44) + ) + (func $_memchr (param $0 i32) (param $1 i32) (param $2 i32) (result i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (set_local $5 + (i32.and + (get_local $1) + (i32.const 255) + ) + ) + (block $label$break$L8 + (block $__rjti$2 + (if + (i32.and + (tee_local $4 + (i32.ne + (get_local $2) + (i32.const 0) + ) + ) + (i32.ne + (i32.and + (get_local $0) + (i32.const 3) + ) + (i32.const 0) + ) + ) + (block + (set_local $4 + (i32.and + (get_local $1) + (i32.const 255) + ) + ) + (set_local $3 + (get_local $2) + ) + (set_local $2 + (get_local $0) + ) + (loop $while-in + (if + (i32.eq + (i32.load8_u + (get_local $2) + ) + (i32.and + (get_local $4) + (i32.const 255) + ) + ) + (block + (set_local $0 + (get_local $3) + ) + (br $__rjti$2) + ) + ) + (br_if $while-in + (i32.and + (tee_local $0 + (i32.ne + (tee_local $3 + (i32.add + (get_local $3) + (i32.const -1) + ) + ) + (i32.const 0) + ) + ) + (i32.ne + (i32.and + (tee_local $2 + (i32.add + (get_local $2) + (i32.const 1) + ) + ) + (i32.const 3) + ) + (i32.const 0) + ) + ) + ) + ) + ) + (block + (set_local $3 + (get_local $2) + ) + (set_local $2 + (get_local $0) + ) + (set_local $0 + (get_local $4) + ) + ) + ) + (if + (get_local $0) + (block + (set_local $0 + (get_local $3) + ) + (br $__rjti$2) + ) + (set_local $0 + (i32.const 0) + ) + ) + (br $label$break$L8) + ) + (if + (i32.ne + (i32.load8_u + (get_local $2) + ) + (tee_local $1 + (i32.and + (get_local $1) + (i32.const 255) + ) + ) + ) + (block + (set_local $3 + (i32.mul + (get_local $5) + (i32.const 16843009) + ) + ) + (block $__rjto$0 + (block $__rjti$0 + (br_if $__rjti$0 + (i32.le_u + (get_local $0) + (i32.const 3) + ) + ) + (loop $while-in3 + (if + (i32.eqz + (i32.and + (i32.xor + (i32.and + (tee_local $4 + (i32.xor + (i32.load + (get_local $2) + ) + (get_local $3) + ) + ) + (i32.const -2139062144) + ) + (i32.const -2139062144) + ) + (i32.add + (get_local $4) + (i32.const -16843009) + ) + ) + ) + (block + (set_local $2 + (i32.add + (get_local $2) + (i32.const 4) + ) + ) + (br_if $while-in3 + (i32.gt_u + (tee_local $0 + (i32.add + (get_local $0) + (i32.const -4) + ) + ) + (i32.const 3) + ) + ) + (br $__rjti$0) + ) + ) + ) + (br $__rjto$0) + ) + (if + (i32.eqz + (get_local $0) + ) + (block + (set_local $0 + (i32.const 0) + ) + (br $label$break$L8) + ) + ) + ) + (loop $while-in5 + (br_if $label$break$L8 + (i32.eq + (i32.load8_u + (get_local $2) + ) + (i32.and + (get_local $1) + (i32.const 255) + ) + ) + ) + (set_local $2 + (i32.add + (get_local $2) + (i32.const 1) + ) + ) + (br_if $while-in5 + (tee_local $0 + (i32.add + (get_local $0) + (i32.const -1) + ) + ) + ) + (set_local $0 + (i32.const 0) + ) + ) + ) + ) + ) + (select + (get_local $2) + (i32.const 0) + (get_local $0) + ) + ) (func $keepAlive (call $loads) (call $stores) @@ -410,6 +729,18 @@ (i32.const 0) (i32.const 0) ) + (drop + (call $_memchr + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + (drop + (call $switch64TOOMUCH + (i64.const 0) + ) + ) ) (func $legalstub$illegalParam (param $0 i32) (param $1 i32) (param $2 i32) (param $3 f64) (call $illegalParam diff --git a/test/wasm-only.fromasm.clamp b/test/wasm-only.fromasm.clamp index 6fa30ba12..3a98e84a3 100644 --- a/test/wasm-only.fromasm.clamp +++ b/test/wasm-only.fromasm.clamp @@ -365,6 +365,325 @@ ) ) ) + (func $switch64TOOMUCH (param $0 i64) (result i32) + (local $1 i32) + (local $2 i64) + (block $switch-default + (if + (i64.ne + (tee_local $2 + (get_local $0) + ) + (i64.const -9223372036854775808) + ) + (br_if $switch-default + (i64.ne + (get_local $2) + (i64.const 4611686018427387904) + ) + ) + ) + (return + (i32.const 40) + ) + ) + (block $switch-default4 + (if + (i32.ne + (tee_local $1 + (i32.const 100) + ) + (i32.const 214748364) + ) + (br_if $switch-default4 + (i32.ne + (get_local $1) + (i32.const 107374182) + ) + ) + ) + (return + (i32.const 41) + ) + ) + (block $switch5 + (if + (i64.ne + (get_local $0) + (i64.const -9223372036854775808) + ) + (br_if $switch5 + (i64.ne + (get_local $0) + (i64.const 4611686018427387904) + ) + ) + ) + (return + (i32.const 42) + ) + ) + (block $switch8 + (if + (i32.ne + (tee_local $1 + (i32.const 100) + ) + (i32.const 214748364) + ) + (br_if $switch8 + (i32.ne + (get_local $1) + (i32.const 107374182) + ) + ) + ) + (return + (i32.const 43) + ) + ) + (i32.const 44) + ) + (func $_memchr (param $0 i32) (param $1 i32) (param $2 i32) (result i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (set_local $5 + (i32.and + (get_local $1) + (i32.const 255) + ) + ) + (block $label$break$L8 + (block $__rjti$2 + (if + (i32.and + (tee_local $4 + (i32.ne + (get_local $2) + (i32.const 0) + ) + ) + (i32.ne + (i32.and + (get_local $0) + (i32.const 3) + ) + (i32.const 0) + ) + ) + (block + (set_local $4 + (i32.and + (get_local $1) + (i32.const 255) + ) + ) + (set_local $3 + (get_local $2) + ) + (set_local $2 + (get_local $0) + ) + (loop $while-in + (if + (i32.eq + (i32.load8_u + (get_local $2) + ) + (i32.and + (get_local $4) + (i32.const 255) + ) + ) + (block + (set_local $0 + (get_local $3) + ) + (br $__rjti$2) + ) + ) + (br_if $while-in + (i32.and + (tee_local $0 + (i32.ne + (tee_local $3 + (i32.add + (get_local $3) + (i32.const -1) + ) + ) + (i32.const 0) + ) + ) + (i32.ne + (i32.and + (tee_local $2 + (i32.add + (get_local $2) + (i32.const 1) + ) + ) + (i32.const 3) + ) + (i32.const 0) + ) + ) + ) + ) + ) + (block + (set_local $3 + (get_local $2) + ) + (set_local $2 + (get_local $0) + ) + (set_local $0 + (get_local $4) + ) + ) + ) + (if + (get_local $0) + (block + (set_local $0 + (get_local $3) + ) + (br $__rjti$2) + ) + (set_local $0 + (i32.const 0) + ) + ) + (br $label$break$L8) + ) + (if + (i32.ne + (i32.load8_u + (get_local $2) + ) + (tee_local $1 + (i32.and + (get_local $1) + (i32.const 255) + ) + ) + ) + (block + (set_local $3 + (i32.mul + (get_local $5) + (i32.const 16843009) + ) + ) + (block $__rjto$0 + (block $__rjti$0 + (br_if $__rjti$0 + (i32.le_u + (get_local $0) + (i32.const 3) + ) + ) + (loop $while-in3 + (if + (i32.eqz + (i32.and + (i32.xor + (i32.and + (tee_local $4 + (i32.xor + (i32.load + (get_local $2) + ) + (get_local $3) + ) + ) + (i32.const -2139062144) + ) + (i32.const -2139062144) + ) + (i32.add + (get_local $4) + (i32.const -16843009) + ) + ) + ) + (block + (set_local $2 + (i32.add + (get_local $2) + (i32.const 4) + ) + ) + (br_if $while-in3 + (i32.gt_u + (tee_local $0 + (i32.add + (get_local $0) + (i32.const -4) + ) + ) + (i32.const 3) + ) + ) + (br $__rjti$0) + ) + ) + ) + (br $__rjto$0) + ) + (if + (i32.eqz + (get_local $0) + ) + (block + (set_local $0 + (i32.const 0) + ) + (br $label$break$L8) + ) + ) + ) + (loop $while-in5 + (br_if $label$break$L8 + (i32.eq + (i32.load8_u + (get_local $2) + ) + (i32.and + (get_local $1) + (i32.const 255) + ) + ) + ) + (set_local $2 + (i32.add + (get_local $2) + (i32.const 1) + ) + ) + (br_if $while-in5 + (tee_local $0 + (i32.add + (get_local $0) + (i32.const -1) + ) + ) + ) + (set_local $0 + (i32.const 0) + ) + ) + ) + ) + ) + (select + (get_local $2) + (i32.const 0) + (get_local $0) + ) + ) (func $keepAlive (call $loads) (call $stores) @@ -410,6 +729,18 @@ (i32.const 0) (i32.const 0) ) + (drop + (call $_memchr + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + (drop + (call $switch64TOOMUCH + (i64.const 0) + ) + ) ) (func $legalstub$illegalParam (param $0 i32) (param $1 i32) (param $2 i32) (param $3 f64) (call $illegalParam diff --git a/test/wasm-only.fromasm.clamp.no-opts b/test/wasm-only.fromasm.clamp.no-opts index 3bcbf33d5..da52d1955 100644 --- a/test/wasm-only.fromasm.clamp.no-opts +++ b/test/wasm-only.fromasm.clamp.no-opts @@ -6,6 +6,7 @@ (type $legaltype$illegalImportResult (func (result i32))) (type $legaltype$_fabsf (func (param f64) (result f64))) (type $legaltype$do_i64 (func (result i32))) + (import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32)) (import "env" "illegalImport" (func $illegalImport (param f64 i64 i32))) (import "env" "illegalImportResult" (func $illegalImportResult (result i64))) (import "env" "_fabsf" (func $_fabsf (param f32) (result f32))) @@ -18,6 +19,7 @@ (import "env" "table" (table 3 3 anyfunc)) (import "env" "memoryBase" (global $memoryBase i32)) (import "env" "tableBase" (global $tableBase i32)) + (global $STACKTOP (mut i32) (get_global $STACKTOP$asm2wasm$import)) (global $tempRet0 (mut i32) (i32.const 0)) (elem (get_global $tableBase) $legalfunc$illegalImport $legalfunc$_fabsf $legalfunc$do_i64) (export "test64" (func $test64)) @@ -950,6 +952,584 @@ (i32.const 44) ) ) + (func $_memchr (param $$src i32) (param $$c i32) (param $$n i32) (result i32) + (local $$0 i32) + (local $$1 i32) + (local $$2 i32) + (local $$3 i32) + (local $$4 i32) + (local $$5 i32) + (local $$6 i32) + (local $$7 i32) + (local $$and i32) + (local $$and15 i32) + (local $$and16 i32) + (local $$and39 i32) + (local $$cmp i32) + (local $$cmp11 i32) + (local $$cmp1132 i32) + (local $$cmp28 i32) + (local $$cmp8 i32) + (local $$cond i32) + (local $$conv1 i32) + (local $$dec i32) + (local $$dec34 i32) + (local $$incdec$ptr i32) + (local $$incdec$ptr21 i32) + (local $$incdec$ptr33 i32) + (local $$lnot i32) + (local $$mul i32) + (local $$n$addr$0$lcssa i32) + (local $$n$addr$0$lcssa52 i32) + (local $$n$addr$043 i32) + (local $$n$addr$1$lcssa i32) + (local $$n$addr$133 i32) + (local $$n$addr$227 i32) + (local $$n$addr$3 i32) + (local $$neg i32) + (local $$or$cond i32) + (local $$or$cond42 i32) + (local $$s$0$lcssa i32) + (local $$s$0$lcssa53 i32) + (local $$s$044 i32) + (local $$s$128 i32) + (local $$s$2 i32) + (local $$sub i32) + (local $$sub22 i32) + (local $$tobool i32) + (local $$tobool2 i32) + (local $$tobool2$lcssa i32) + (local $$tobool241 i32) + (local $$tobool25 i32) + (local $$tobool2526 i32) + (local $$tobool36 i32) + (local $$tobool40 i32) + (local $$w$0$lcssa i32) + (local $$w$034 i32) + (local $$xor i32) + (local $label i32) + (local $sp i32) + (set_local $sp + (get_global $STACKTOP) + ) + (set_local $$conv1 + (i32.and + (get_local $$c) + (i32.const 255) + ) + ) + (set_local $$0 + (get_local $$src) + ) + (set_local $$and39 + (i32.and + (get_local $$0) + (i32.const 3) + ) + ) + (set_local $$tobool40 + (i32.ne + (get_local $$and39) + (i32.const 0) + ) + ) + (set_local $$tobool241 + (i32.ne + (get_local $$n) + (i32.const 0) + ) + ) + (set_local $$or$cond42 + (i32.and + (get_local $$tobool241) + (get_local $$tobool40) + ) + ) + (block $label$break$L1 + (if + (get_local $$or$cond42) + (block + (set_local $$1 + (i32.and + (get_local $$c) + (i32.const 255) + ) + ) + (set_local $$n$addr$043 + (get_local $$n) + ) + (set_local $$s$044 + (get_local $$src) + ) + (loop $while-in + (block $while-out + (set_local $$2 + (i32.load8_s + (get_local $$s$044) + ) + ) + (set_local $$cmp + (i32.eq + (i32.shr_s + (i32.shl + (get_local $$2) + (i32.const 24) + ) + (i32.const 24) + ) + (i32.shr_s + (i32.shl + (get_local $$1) + (i32.const 24) + ) + (i32.const 24) + ) + ) + ) + (if + (get_local $$cmp) + (block + (set_local $$n$addr$0$lcssa52 + (get_local $$n$addr$043) + ) + (set_local $$s$0$lcssa53 + (get_local $$s$044) + ) + (set_local $label + (i32.const 6) + ) + (br $label$break$L1) + ) + ) + (set_local $$incdec$ptr + (i32.add + (get_local $$s$044) + (i32.const 1) + ) + ) + (set_local $$dec + (i32.add + (get_local $$n$addr$043) + (i32.const -1) + ) + ) + (set_local $$3 + (get_local $$incdec$ptr) + ) + (set_local $$and + (i32.and + (get_local $$3) + (i32.const 3) + ) + ) + (set_local $$tobool + (i32.ne + (get_local $$and) + (i32.const 0) + ) + ) + (set_local $$tobool2 + (i32.ne + (get_local $$dec) + (i32.const 0) + ) + ) + (set_local $$or$cond + (i32.and + (get_local $$tobool2) + (get_local $$tobool) + ) + ) + (if + (get_local $$or$cond) + (block + (set_local $$n$addr$043 + (get_local $$dec) + ) + (set_local $$s$044 + (get_local $$incdec$ptr) + ) + ) + (block + (set_local $$n$addr$0$lcssa + (get_local $$dec) + ) + (set_local $$s$0$lcssa + (get_local $$incdec$ptr) + ) + (set_local $$tobool2$lcssa + (get_local $$tobool2) + ) + (set_local $label + (i32.const 5) + ) + (br $while-out) + ) + ) + (br $while-in) + ) + ) + ) + (block + (set_local $$n$addr$0$lcssa + (get_local $$n) + ) + (set_local $$s$0$lcssa + (get_local $$src) + ) + (set_local $$tobool2$lcssa + (get_local $$tobool241) + ) + (set_local $label + (i32.const 5) + ) + ) + ) + ) + (if + (i32.eq + (get_local $label) + (i32.const 5) + ) + (if + (get_local $$tobool2$lcssa) + (block + (set_local $$n$addr$0$lcssa52 + (get_local $$n$addr$0$lcssa) + ) + (set_local $$s$0$lcssa53 + (get_local $$s$0$lcssa) + ) + (set_local $label + (i32.const 6) + ) + ) + (block + (set_local $$n$addr$3 + (i32.const 0) + ) + (set_local $$s$2 + (get_local $$s$0$lcssa) + ) + ) + ) + ) + (block $label$break$L8 + (if + (i32.eq + (get_local $label) + (i32.const 6) + ) + (block + (set_local $$4 + (i32.load8_s + (get_local $$s$0$lcssa53) + ) + ) + (set_local $$5 + (i32.and + (get_local $$c) + (i32.const 255) + ) + ) + (set_local $$cmp8 + (i32.eq + (i32.shr_s + (i32.shl + (get_local $$4) + (i32.const 24) + ) + (i32.const 24) + ) + (i32.shr_s + (i32.shl + (get_local $$5) + (i32.const 24) + ) + (i32.const 24) + ) + ) + ) + (if + (get_local $$cmp8) + (block + (set_local $$n$addr$3 + (get_local $$n$addr$0$lcssa52) + ) + (set_local $$s$2 + (get_local $$s$0$lcssa53) + ) + ) + (block + (set_local $$mul + (i32.mul + (get_local $$conv1) + (i32.const 16843009) + ) + ) + (set_local $$cmp1132 + (i32.gt_u + (get_local $$n$addr$0$lcssa52) + (i32.const 3) + ) + ) + (block $label$break$L11 + (if + (get_local $$cmp1132) + (block + (set_local $$n$addr$133 + (get_local $$n$addr$0$lcssa52) + ) + (set_local $$w$034 + (get_local $$s$0$lcssa53) + ) + (loop $while-in3 + (block $while-out2 + (set_local $$6 + (i32.load + (get_local $$w$034) + ) + ) + (set_local $$xor + (i32.xor + (get_local $$6) + (get_local $$mul) + ) + ) + (set_local $$sub + (i32.add + (get_local $$xor) + (i32.const -16843009) + ) + ) + (set_local $$neg + (i32.and + (get_local $$xor) + (i32.const -2139062144) + ) + ) + (set_local $$and15 + (i32.xor + (get_local $$neg) + (i32.const -2139062144) + ) + ) + (set_local $$and16 + (i32.and + (get_local $$and15) + (get_local $$sub) + ) + ) + (set_local $$lnot + (i32.eq + (get_local $$and16) + (i32.const 0) + ) + ) + (if + (i32.eqz + (get_local $$lnot) + ) + (br $while-out2) + ) + (set_local $$incdec$ptr21 + (i32.add + (get_local $$w$034) + (i32.const 4) + ) + ) + (set_local $$sub22 + (i32.add + (get_local $$n$addr$133) + (i32.const -4) + ) + ) + (set_local $$cmp11 + (i32.gt_u + (get_local $$sub22) + (i32.const 3) + ) + ) + (if + (get_local $$cmp11) + (block + (set_local $$n$addr$133 + (get_local $$sub22) + ) + (set_local $$w$034 + (get_local $$incdec$ptr21) + ) + ) + (block + (set_local $$n$addr$1$lcssa + (get_local $$sub22) + ) + (set_local $$w$0$lcssa + (get_local $$incdec$ptr21) + ) + (set_local $label + (i32.const 11) + ) + (br $label$break$L11) + ) + ) + (br $while-in3) + ) + ) + (set_local $$n$addr$227 + (get_local $$n$addr$133) + ) + (set_local $$s$128 + (get_local $$w$034) + ) + ) + (block + (set_local $$n$addr$1$lcssa + (get_local $$n$addr$0$lcssa52) + ) + (set_local $$w$0$lcssa + (get_local $$s$0$lcssa53) + ) + (set_local $label + (i32.const 11) + ) + ) + ) + ) + (if + (i32.eq + (get_local $label) + (i32.const 11) + ) + (block + (set_local $$tobool2526 + (i32.eq + (get_local $$n$addr$1$lcssa) + (i32.const 0) + ) + ) + (if + (get_local $$tobool2526) + (block + (set_local $$n$addr$3 + (i32.const 0) + ) + (set_local $$s$2 + (get_local $$w$0$lcssa) + ) + (br $label$break$L8) + ) + (block + (set_local $$n$addr$227 + (get_local $$n$addr$1$lcssa) + ) + (set_local $$s$128 + (get_local $$w$0$lcssa) + ) + ) + ) + ) + ) + (loop $while-in5 + (block $while-out4 + (set_local $$7 + (i32.load8_s + (get_local $$s$128) + ) + ) + (set_local $$cmp28 + (i32.eq + (i32.shr_s + (i32.shl + (get_local $$7) + (i32.const 24) + ) + (i32.const 24) + ) + (i32.shr_s + (i32.shl + (get_local $$5) + (i32.const 24) + ) + (i32.const 24) + ) + ) + ) + (if + (get_local $$cmp28) + (block + (set_local $$n$addr$3 + (get_local $$n$addr$227) + ) + (set_local $$s$2 + (get_local $$s$128) + ) + (br $label$break$L8) + ) + ) + (set_local $$incdec$ptr33 + (i32.add + (get_local $$s$128) + (i32.const 1) + ) + ) + (set_local $$dec34 + (i32.add + (get_local $$n$addr$227) + (i32.const -1) + ) + ) + (set_local $$tobool25 + (i32.eq + (get_local $$dec34) + (i32.const 0) + ) + ) + (if + (get_local $$tobool25) + (block + (set_local $$n$addr$3 + (i32.const 0) + ) + (set_local $$s$2 + (get_local $$incdec$ptr33) + ) + (br $while-out4) + ) + (block + (set_local $$n$addr$227 + (get_local $$dec34) + ) + (set_local $$s$128 + (get_local $$incdec$ptr33) + ) + ) + ) + (br $while-in5) + ) + ) + ) + ) + ) + ) + ) + (set_local $$tobool36 + (i32.ne + (get_local $$n$addr$3) + (i32.const 0) + ) + ) + (set_local $$cond + (if i32 + (get_local $$tobool36) + (get_local $$s$2) + (i32.const 0) + ) + ) + (return + (get_local $$cond) + ) + ) (func $keepAlive (call $loads) (call $stores) @@ -995,6 +1575,18 @@ (i32.const 0) (i32.const 0) ) + (drop + (call $_memchr + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + (drop + (call $switch64TOOMUCH + (i64.const 0) + ) + ) ) (func $__emscripten_dceable_type_decls (drop diff --git a/test/wasm-only.fromasm.imprecise b/test/wasm-only.fromasm.imprecise index 11eedc71d..de241740e 100644 --- a/test/wasm-only.fromasm.imprecise +++ b/test/wasm-only.fromasm.imprecise @@ -280,6 +280,325 @@ ) ) ) + (func $switch64TOOMUCH (param $0 i64) (result i32) + (local $1 i32) + (local $2 i64) + (block $switch-default + (if + (i64.ne + (tee_local $2 + (get_local $0) + ) + (i64.const -9223372036854775808) + ) + (br_if $switch-default + (i64.ne + (get_local $2) + (i64.const 4611686018427387904) + ) + ) + ) + (return + (i32.const 40) + ) + ) + (block $switch-default4 + (if + (i32.ne + (tee_local $1 + (i32.const 100) + ) + (i32.const 214748364) + ) + (br_if $switch-default4 + (i32.ne + (get_local $1) + (i32.const 107374182) + ) + ) + ) + (return + (i32.const 41) + ) + ) + (block $switch5 + (if + (i64.ne + (get_local $0) + (i64.const -9223372036854775808) + ) + (br_if $switch5 + (i64.ne + (get_local $0) + (i64.const 4611686018427387904) + ) + ) + ) + (return + (i32.const 42) + ) + ) + (block $switch8 + (if + (i32.ne + (tee_local $1 + (i32.const 100) + ) + (i32.const 214748364) + ) + (br_if $switch8 + (i32.ne + (get_local $1) + (i32.const 107374182) + ) + ) + ) + (return + (i32.const 43) + ) + ) + (i32.const 44) + ) + (func $_memchr (param $0 i32) (param $1 i32) (param $2 i32) (result i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (set_local $5 + (i32.and + (get_local $1) + (i32.const 255) + ) + ) + (block $label$break$L8 + (block $__rjti$2 + (if + (i32.and + (tee_local $4 + (i32.ne + (get_local $2) + (i32.const 0) + ) + ) + (i32.ne + (i32.and + (get_local $0) + (i32.const 3) + ) + (i32.const 0) + ) + ) + (block + (set_local $4 + (i32.and + (get_local $1) + (i32.const 255) + ) + ) + (set_local $3 + (get_local $2) + ) + (set_local $2 + (get_local $0) + ) + (loop $while-in + (if + (i32.eq + (i32.load8_u + (get_local $2) + ) + (i32.and + (get_local $4) + (i32.const 255) + ) + ) + (block + (set_local $0 + (get_local $3) + ) + (br $__rjti$2) + ) + ) + (br_if $while-in + (i32.and + (tee_local $0 + (i32.ne + (tee_local $3 + (i32.add + (get_local $3) + (i32.const -1) + ) + ) + (i32.const 0) + ) + ) + (i32.ne + (i32.and + (tee_local $2 + (i32.add + (get_local $2) + (i32.const 1) + ) + ) + (i32.const 3) + ) + (i32.const 0) + ) + ) + ) + ) + ) + (block + (set_local $3 + (get_local $2) + ) + (set_local $2 + (get_local $0) + ) + (set_local $0 + (get_local $4) + ) + ) + ) + (if + (get_local $0) + (block + (set_local $0 + (get_local $3) + ) + (br $__rjti$2) + ) + (set_local $0 + (i32.const 0) + ) + ) + (br $label$break$L8) + ) + (if + (i32.ne + (i32.load8_u + (get_local $2) + ) + (tee_local $1 + (i32.and + (get_local $1) + (i32.const 255) + ) + ) + ) + (block + (set_local $3 + (i32.mul + (get_local $5) + (i32.const 16843009) + ) + ) + (block $__rjto$0 + (block $__rjti$0 + (br_if $__rjti$0 + (i32.le_u + (get_local $0) + (i32.const 3) + ) + ) + (loop $while-in3 + (if + (i32.eqz + (i32.and + (i32.xor + (i32.and + (tee_local $4 + (i32.xor + (i32.load + (get_local $2) + ) + (get_local $3) + ) + ) + (i32.const -2139062144) + ) + (i32.const -2139062144) + ) + (i32.add + (get_local $4) + (i32.const -16843009) + ) + ) + ) + (block + (set_local $2 + (i32.add + (get_local $2) + (i32.const 4) + ) + ) + (br_if $while-in3 + (i32.gt_u + (tee_local $0 + (i32.add + (get_local $0) + (i32.const -4) + ) + ) + (i32.const 3) + ) + ) + (br $__rjti$0) + ) + ) + ) + (br $__rjto$0) + ) + (if + (i32.eqz + (get_local $0) + ) + (block + (set_local $0 + (i32.const 0) + ) + (br $label$break$L8) + ) + ) + ) + (loop $while-in5 + (br_if $label$break$L8 + (i32.eq + (i32.load8_u + (get_local $2) + ) + (i32.and + (get_local $1) + (i32.const 255) + ) + ) + ) + (set_local $2 + (i32.add + (get_local $2) + (i32.const 1) + ) + ) + (br_if $while-in5 + (tee_local $0 + (i32.add + (get_local $0) + (i32.const -1) + ) + ) + ) + (set_local $0 + (i32.const 0) + ) + ) + ) + ) + ) + (select + (get_local $2) + (i32.const 0) + (get_local $0) + ) + ) (func $keepAlive (call $loads) (call $stores) @@ -325,6 +644,18 @@ (i32.const 0) (i32.const 0) ) + (drop + (call $_memchr + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + (drop + (call $switch64TOOMUCH + (i64.const 0) + ) + ) ) (func $legalstub$illegalParam (param $0 i32) (param $1 i32) (param $2 i32) (param $3 f64) (call $illegalParam diff --git a/test/wasm-only.fromasm.imprecise.no-opts b/test/wasm-only.fromasm.imprecise.no-opts index 2ab7a85ca..5bdb5bc00 100644 --- a/test/wasm-only.fromasm.imprecise.no-opts +++ b/test/wasm-only.fromasm.imprecise.no-opts @@ -6,6 +6,7 @@ (type $legaltype$illegalImportResult (func (result i32))) (type $legaltype$_fabsf (func (param f64) (result f64))) (type $legaltype$do_i64 (func (result i32))) + (import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32)) (import "env" "illegalImport" (func $illegalImport (param f64 i64 i32))) (import "env" "illegalImportResult" (func $illegalImportResult (result i64))) (import "env" "_fabsf" (func $_fabsf (param f32) (result f32))) @@ -18,6 +19,7 @@ (import "env" "table" (table 3 3 anyfunc)) (import "env" "memoryBase" (global $memoryBase i32)) (import "env" "tableBase" (global $tableBase i32)) + (global $STACKTOP (mut i32) (get_global $STACKTOP$asm2wasm$import)) (global $tempRet0 (mut i32) (i32.const 0)) (elem (get_global $tableBase) $legalfunc$illegalImport $legalfunc$_fabsf $legalfunc$do_i64) (export "test64" (func $test64)) @@ -889,6 +891,584 @@ (i32.const 44) ) ) + (func $_memchr (param $$src i32) (param $$c i32) (param $$n i32) (result i32) + (local $$0 i32) + (local $$1 i32) + (local $$2 i32) + (local $$3 i32) + (local $$4 i32) + (local $$5 i32) + (local $$6 i32) + (local $$7 i32) + (local $$and i32) + (local $$and15 i32) + (local $$and16 i32) + (local $$and39 i32) + (local $$cmp i32) + (local $$cmp11 i32) + (local $$cmp1132 i32) + (local $$cmp28 i32) + (local $$cmp8 i32) + (local $$cond i32) + (local $$conv1 i32) + (local $$dec i32) + (local $$dec34 i32) + (local $$incdec$ptr i32) + (local $$incdec$ptr21 i32) + (local $$incdec$ptr33 i32) + (local $$lnot i32) + (local $$mul i32) + (local $$n$addr$0$lcssa i32) + (local $$n$addr$0$lcssa52 i32) + (local $$n$addr$043 i32) + (local $$n$addr$1$lcssa i32) + (local $$n$addr$133 i32) + (local $$n$addr$227 i32) + (local $$n$addr$3 i32) + (local $$neg i32) + (local $$or$cond i32) + (local $$or$cond42 i32) + (local $$s$0$lcssa i32) + (local $$s$0$lcssa53 i32) + (local $$s$044 i32) + (local $$s$128 i32) + (local $$s$2 i32) + (local $$sub i32) + (local $$sub22 i32) + (local $$tobool i32) + (local $$tobool2 i32) + (local $$tobool2$lcssa i32) + (local $$tobool241 i32) + (local $$tobool25 i32) + (local $$tobool2526 i32) + (local $$tobool36 i32) + (local $$tobool40 i32) + (local $$w$0$lcssa i32) + (local $$w$034 i32) + (local $$xor i32) + (local $label i32) + (local $sp i32) + (set_local $sp + (get_global $STACKTOP) + ) + (set_local $$conv1 + (i32.and + (get_local $$c) + (i32.const 255) + ) + ) + (set_local $$0 + (get_local $$src) + ) + (set_local $$and39 + (i32.and + (get_local $$0) + (i32.const 3) + ) + ) + (set_local $$tobool40 + (i32.ne + (get_local $$and39) + (i32.const 0) + ) + ) + (set_local $$tobool241 + (i32.ne + (get_local $$n) + (i32.const 0) + ) + ) + (set_local $$or$cond42 + (i32.and + (get_local $$tobool241) + (get_local $$tobool40) + ) + ) + (block $label$break$L1 + (if + (get_local $$or$cond42) + (block + (set_local $$1 + (i32.and + (get_local $$c) + (i32.const 255) + ) + ) + (set_local $$n$addr$043 + (get_local $$n) + ) + (set_local $$s$044 + (get_local $$src) + ) + (loop $while-in + (block $while-out + (set_local $$2 + (i32.load8_s + (get_local $$s$044) + ) + ) + (set_local $$cmp + (i32.eq + (i32.shr_s + (i32.shl + (get_local $$2) + (i32.const 24) + ) + (i32.const 24) + ) + (i32.shr_s + (i32.shl + (get_local $$1) + (i32.const 24) + ) + (i32.const 24) + ) + ) + ) + (if + (get_local $$cmp) + (block + (set_local $$n$addr$0$lcssa52 + (get_local $$n$addr$043) + ) + (set_local $$s$0$lcssa53 + (get_local $$s$044) + ) + (set_local $label + (i32.const 6) + ) + (br $label$break$L1) + ) + ) + (set_local $$incdec$ptr + (i32.add + (get_local $$s$044) + (i32.const 1) + ) + ) + (set_local $$dec + (i32.add + (get_local $$n$addr$043) + (i32.const -1) + ) + ) + (set_local $$3 + (get_local $$incdec$ptr) + ) + (set_local $$and + (i32.and + (get_local $$3) + (i32.const 3) + ) + ) + (set_local $$tobool + (i32.ne + (get_local $$and) + (i32.const 0) + ) + ) + (set_local $$tobool2 + (i32.ne + (get_local $$dec) + (i32.const 0) + ) + ) + (set_local $$or$cond + (i32.and + (get_local $$tobool2) + (get_local $$tobool) + ) + ) + (if + (get_local $$or$cond) + (block + (set_local $$n$addr$043 + (get_local $$dec) + ) + (set_local $$s$044 + (get_local $$incdec$ptr) + ) + ) + (block + (set_local $$n$addr$0$lcssa + (get_local $$dec) + ) + (set_local $$s$0$lcssa + (get_local $$incdec$ptr) + ) + (set_local $$tobool2$lcssa + (get_local $$tobool2) + ) + (set_local $label + (i32.const 5) + ) + (br $while-out) + ) + ) + (br $while-in) + ) + ) + ) + (block + (set_local $$n$addr$0$lcssa + (get_local $$n) + ) + (set_local $$s$0$lcssa + (get_local $$src) + ) + (set_local $$tobool2$lcssa + (get_local $$tobool241) + ) + (set_local $label + (i32.const 5) + ) + ) + ) + ) + (if + (i32.eq + (get_local $label) + (i32.const 5) + ) + (if + (get_local $$tobool2$lcssa) + (block + (set_local $$n$addr$0$lcssa52 + (get_local $$n$addr$0$lcssa) + ) + (set_local $$s$0$lcssa53 + (get_local $$s$0$lcssa) + ) + (set_local $label + (i32.const 6) + ) + ) + (block + (set_local $$n$addr$3 + (i32.const 0) + ) + (set_local $$s$2 + (get_local $$s$0$lcssa) + ) + ) + ) + ) + (block $label$break$L8 + (if + (i32.eq + (get_local $label) + (i32.const 6) + ) + (block + (set_local $$4 + (i32.load8_s + (get_local $$s$0$lcssa53) + ) + ) + (set_local $$5 + (i32.and + (get_local $$c) + (i32.const 255) + ) + ) + (set_local $$cmp8 + (i32.eq + (i32.shr_s + (i32.shl + (get_local $$4) + (i32.const 24) + ) + (i32.const 24) + ) + (i32.shr_s + (i32.shl + (get_local $$5) + (i32.const 24) + ) + (i32.const 24) + ) + ) + ) + (if + (get_local $$cmp8) + (block + (set_local $$n$addr$3 + (get_local $$n$addr$0$lcssa52) + ) + (set_local $$s$2 + (get_local $$s$0$lcssa53) + ) + ) + (block + (set_local $$mul + (i32.mul + (get_local $$conv1) + (i32.const 16843009) + ) + ) + (set_local $$cmp1132 + (i32.gt_u + (get_local $$n$addr$0$lcssa52) + (i32.const 3) + ) + ) + (block $label$break$L11 + (if + (get_local $$cmp1132) + (block + (set_local $$n$addr$133 + (get_local $$n$addr$0$lcssa52) + ) + (set_local $$w$034 + (get_local $$s$0$lcssa53) + ) + (loop $while-in3 + (block $while-out2 + (set_local $$6 + (i32.load + (get_local $$w$034) + ) + ) + (set_local $$xor + (i32.xor + (get_local $$6) + (get_local $$mul) + ) + ) + (set_local $$sub + (i32.add + (get_local $$xor) + (i32.const -16843009) + ) + ) + (set_local $$neg + (i32.and + (get_local $$xor) + (i32.const -2139062144) + ) + ) + (set_local $$and15 + (i32.xor + (get_local $$neg) + (i32.const -2139062144) + ) + ) + (set_local $$and16 + (i32.and + (get_local $$and15) + (get_local $$sub) + ) + ) + (set_local $$lnot + (i32.eq + (get_local $$and16) + (i32.const 0) + ) + ) + (if + (i32.eqz + (get_local $$lnot) + ) + (br $while-out2) + ) + (set_local $$incdec$ptr21 + (i32.add + (get_local $$w$034) + (i32.const 4) + ) + ) + (set_local $$sub22 + (i32.add + (get_local $$n$addr$133) + (i32.const -4) + ) + ) + (set_local $$cmp11 + (i32.gt_u + (get_local $$sub22) + (i32.const 3) + ) + ) + (if + (get_local $$cmp11) + (block + (set_local $$n$addr$133 + (get_local $$sub22) + ) + (set_local $$w$034 + (get_local $$incdec$ptr21) + ) + ) + (block + (set_local $$n$addr$1$lcssa + (get_local $$sub22) + ) + (set_local $$w$0$lcssa + (get_local $$incdec$ptr21) + ) + (set_local $label + (i32.const 11) + ) + (br $label$break$L11) + ) + ) + (br $while-in3) + ) + ) + (set_local $$n$addr$227 + (get_local $$n$addr$133) + ) + (set_local $$s$128 + (get_local $$w$034) + ) + ) + (block + (set_local $$n$addr$1$lcssa + (get_local $$n$addr$0$lcssa52) + ) + (set_local $$w$0$lcssa + (get_local $$s$0$lcssa53) + ) + (set_local $label + (i32.const 11) + ) + ) + ) + ) + (if + (i32.eq + (get_local $label) + (i32.const 11) + ) + (block + (set_local $$tobool2526 + (i32.eq + (get_local $$n$addr$1$lcssa) + (i32.const 0) + ) + ) + (if + (get_local $$tobool2526) + (block + (set_local $$n$addr$3 + (i32.const 0) + ) + (set_local $$s$2 + (get_local $$w$0$lcssa) + ) + (br $label$break$L8) + ) + (block + (set_local $$n$addr$227 + (get_local $$n$addr$1$lcssa) + ) + (set_local $$s$128 + (get_local $$w$0$lcssa) + ) + ) + ) + ) + ) + (loop $while-in5 + (block $while-out4 + (set_local $$7 + (i32.load8_s + (get_local $$s$128) + ) + ) + (set_local $$cmp28 + (i32.eq + (i32.shr_s + (i32.shl + (get_local $$7) + (i32.const 24) + ) + (i32.const 24) + ) + (i32.shr_s + (i32.shl + (get_local $$5) + (i32.const 24) + ) + (i32.const 24) + ) + ) + ) + (if + (get_local $$cmp28) + (block + (set_local $$n$addr$3 + (get_local $$n$addr$227) + ) + (set_local $$s$2 + (get_local $$s$128) + ) + (br $label$break$L8) + ) + ) + (set_local $$incdec$ptr33 + (i32.add + (get_local $$s$128) + (i32.const 1) + ) + ) + (set_local $$dec34 + (i32.add + (get_local $$n$addr$227) + (i32.const -1) + ) + ) + (set_local $$tobool25 + (i32.eq + (get_local $$dec34) + (i32.const 0) + ) + ) + (if + (get_local $$tobool25) + (block + (set_local $$n$addr$3 + (i32.const 0) + ) + (set_local $$s$2 + (get_local $$incdec$ptr33) + ) + (br $while-out4) + ) + (block + (set_local $$n$addr$227 + (get_local $$dec34) + ) + (set_local $$s$128 + (get_local $$incdec$ptr33) + ) + ) + ) + (br $while-in5) + ) + ) + ) + ) + ) + ) + ) + (set_local $$tobool36 + (i32.ne + (get_local $$n$addr$3) + (i32.const 0) + ) + ) + (set_local $$cond + (if i32 + (get_local $$tobool36) + (get_local $$s$2) + (i32.const 0) + ) + ) + (return + (get_local $$cond) + ) + ) (func $keepAlive (call $loads) (call $stores) @@ -934,6 +1514,18 @@ (i32.const 0) (i32.const 0) ) + (drop + (call $_memchr + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + (drop + (call $switch64TOOMUCH + (i64.const 0) + ) + ) ) (func $__emscripten_dceable_type_decls (drop diff --git a/test/wasm-only.fromasm.no-opts b/test/wasm-only.fromasm.no-opts index 3bcbf33d5..da52d1955 100644 --- a/test/wasm-only.fromasm.no-opts +++ b/test/wasm-only.fromasm.no-opts @@ -6,6 +6,7 @@ (type $legaltype$illegalImportResult (func (result i32))) (type $legaltype$_fabsf (func (param f64) (result f64))) (type $legaltype$do_i64 (func (result i32))) + (import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32)) (import "env" "illegalImport" (func $illegalImport (param f64 i64 i32))) (import "env" "illegalImportResult" (func $illegalImportResult (result i64))) (import "env" "_fabsf" (func $_fabsf (param f32) (result f32))) @@ -18,6 +19,7 @@ (import "env" "table" (table 3 3 anyfunc)) (import "env" "memoryBase" (global $memoryBase i32)) (import "env" "tableBase" (global $tableBase i32)) + (global $STACKTOP (mut i32) (get_global $STACKTOP$asm2wasm$import)) (global $tempRet0 (mut i32) (i32.const 0)) (elem (get_global $tableBase) $legalfunc$illegalImport $legalfunc$_fabsf $legalfunc$do_i64) (export "test64" (func $test64)) @@ -950,6 +952,584 @@ (i32.const 44) ) ) + (func $_memchr (param $$src i32) (param $$c i32) (param $$n i32) (result i32) + (local $$0 i32) + (local $$1 i32) + (local $$2 i32) + (local $$3 i32) + (local $$4 i32) + (local $$5 i32) + (local $$6 i32) + (local $$7 i32) + (local $$and i32) + (local $$and15 i32) + (local $$and16 i32) + (local $$and39 i32) + (local $$cmp i32) + (local $$cmp11 i32) + (local $$cmp1132 i32) + (local $$cmp28 i32) + (local $$cmp8 i32) + (local $$cond i32) + (local $$conv1 i32) + (local $$dec i32) + (local $$dec34 i32) + (local $$incdec$ptr i32) + (local $$incdec$ptr21 i32) + (local $$incdec$ptr33 i32) + (local $$lnot i32) + (local $$mul i32) + (local $$n$addr$0$lcssa i32) + (local $$n$addr$0$lcssa52 i32) + (local $$n$addr$043 i32) + (local $$n$addr$1$lcssa i32) + (local $$n$addr$133 i32) + (local $$n$addr$227 i32) + (local $$n$addr$3 i32) + (local $$neg i32) + (local $$or$cond i32) + (local $$or$cond42 i32) + (local $$s$0$lcssa i32) + (local $$s$0$lcssa53 i32) + (local $$s$044 i32) + (local $$s$128 i32) + (local $$s$2 i32) + (local $$sub i32) + (local $$sub22 i32) + (local $$tobool i32) + (local $$tobool2 i32) + (local $$tobool2$lcssa i32) + (local $$tobool241 i32) + (local $$tobool25 i32) + (local $$tobool2526 i32) + (local $$tobool36 i32) + (local $$tobool40 i32) + (local $$w$0$lcssa i32) + (local $$w$034 i32) + (local $$xor i32) + (local $label i32) + (local $sp i32) + (set_local $sp + (get_global $STACKTOP) + ) + (set_local $$conv1 + (i32.and + (get_local $$c) + (i32.const 255) + ) + ) + (set_local $$0 + (get_local $$src) + ) + (set_local $$and39 + (i32.and + (get_local $$0) + (i32.const 3) + ) + ) + (set_local $$tobool40 + (i32.ne + (get_local $$and39) + (i32.const 0) + ) + ) + (set_local $$tobool241 + (i32.ne + (get_local $$n) + (i32.const 0) + ) + ) + (set_local $$or$cond42 + (i32.and + (get_local $$tobool241) + (get_local $$tobool40) + ) + ) + (block $label$break$L1 + (if + (get_local $$or$cond42) + (block + (set_local $$1 + (i32.and + (get_local $$c) + (i32.const 255) + ) + ) + (set_local $$n$addr$043 + (get_local $$n) + ) + (set_local $$s$044 + (get_local $$src) + ) + (loop $while-in + (block $while-out + (set_local $$2 + (i32.load8_s + (get_local $$s$044) + ) + ) + (set_local $$cmp + (i32.eq + (i32.shr_s + (i32.shl + (get_local $$2) + (i32.const 24) + ) + (i32.const 24) + ) + (i32.shr_s + (i32.shl + (get_local $$1) + (i32.const 24) + ) + (i32.const 24) + ) + ) + ) + (if + (get_local $$cmp) + (block + (set_local $$n$addr$0$lcssa52 + (get_local $$n$addr$043) + ) + (set_local $$s$0$lcssa53 + (get_local $$s$044) + ) + (set_local $label + (i32.const 6) + ) + (br $label$break$L1) + ) + ) + (set_local $$incdec$ptr + (i32.add + (get_local $$s$044) + (i32.const 1) + ) + ) + (set_local $$dec + (i32.add + (get_local $$n$addr$043) + (i32.const -1) + ) + ) + (set_local $$3 + (get_local $$incdec$ptr) + ) + (set_local $$and + (i32.and + (get_local $$3) + (i32.const 3) + ) + ) + (set_local $$tobool + (i32.ne + (get_local $$and) + (i32.const 0) + ) + ) + (set_local $$tobool2 + (i32.ne + (get_local $$dec) + (i32.const 0) + ) + ) + (set_local $$or$cond + (i32.and + (get_local $$tobool2) + (get_local $$tobool) + ) + ) + (if + (get_local $$or$cond) + (block + (set_local $$n$addr$043 + (get_local $$dec) + ) + (set_local $$s$044 + (get_local $$incdec$ptr) + ) + ) + (block + (set_local $$n$addr$0$lcssa + (get_local $$dec) + ) + (set_local $$s$0$lcssa + (get_local $$incdec$ptr) + ) + (set_local $$tobool2$lcssa + (get_local $$tobool2) + ) + (set_local $label + (i32.const 5) + ) + (br $while-out) + ) + ) + (br $while-in) + ) + ) + ) + (block + (set_local $$n$addr$0$lcssa + (get_local $$n) + ) + (set_local $$s$0$lcssa + (get_local $$src) + ) + (set_local $$tobool2$lcssa + (get_local $$tobool241) + ) + (set_local $label + (i32.const 5) + ) + ) + ) + ) + (if + (i32.eq + (get_local $label) + (i32.const 5) + ) + (if + (get_local $$tobool2$lcssa) + (block + (set_local $$n$addr$0$lcssa52 + (get_local $$n$addr$0$lcssa) + ) + (set_local $$s$0$lcssa53 + (get_local $$s$0$lcssa) + ) + (set_local $label + (i32.const 6) + ) + ) + (block + (set_local $$n$addr$3 + (i32.const 0) + ) + (set_local $$s$2 + (get_local $$s$0$lcssa) + ) + ) + ) + ) + (block $label$break$L8 + (if + (i32.eq + (get_local $label) + (i32.const 6) + ) + (block + (set_local $$4 + (i32.load8_s + (get_local $$s$0$lcssa53) + ) + ) + (set_local $$5 + (i32.and + (get_local $$c) + (i32.const 255) + ) + ) + (set_local $$cmp8 + (i32.eq + (i32.shr_s + (i32.shl + (get_local $$4) + (i32.const 24) + ) + (i32.const 24) + ) + (i32.shr_s + (i32.shl + (get_local $$5) + (i32.const 24) + ) + (i32.const 24) + ) + ) + ) + (if + (get_local $$cmp8) + (block + (set_local $$n$addr$3 + (get_local $$n$addr$0$lcssa52) + ) + (set_local $$s$2 + (get_local $$s$0$lcssa53) + ) + ) + (block + (set_local $$mul + (i32.mul + (get_local $$conv1) + (i32.const 16843009) + ) + ) + (set_local $$cmp1132 + (i32.gt_u + (get_local $$n$addr$0$lcssa52) + (i32.const 3) + ) + ) + (block $label$break$L11 + (if + (get_local $$cmp1132) + (block + (set_local $$n$addr$133 + (get_local $$n$addr$0$lcssa52) + ) + (set_local $$w$034 + (get_local $$s$0$lcssa53) + ) + (loop $while-in3 + (block $while-out2 + (set_local $$6 + (i32.load + (get_local $$w$034) + ) + ) + (set_local $$xor + (i32.xor + (get_local $$6) + (get_local $$mul) + ) + ) + (set_local $$sub + (i32.add + (get_local $$xor) + (i32.const -16843009) + ) + ) + (set_local $$neg + (i32.and + (get_local $$xor) + (i32.const -2139062144) + ) + ) + (set_local $$and15 + (i32.xor + (get_local $$neg) + (i32.const -2139062144) + ) + ) + (set_local $$and16 + (i32.and + (get_local $$and15) + (get_local $$sub) + ) + ) + (set_local $$lnot + (i32.eq + (get_local $$and16) + (i32.const 0) + ) + ) + (if + (i32.eqz + (get_local $$lnot) + ) + (br $while-out2) + ) + (set_local $$incdec$ptr21 + (i32.add + (get_local $$w$034) + (i32.const 4) + ) + ) + (set_local $$sub22 + (i32.add + (get_local $$n$addr$133) + (i32.const -4) + ) + ) + (set_local $$cmp11 + (i32.gt_u + (get_local $$sub22) + (i32.const 3) + ) + ) + (if + (get_local $$cmp11) + (block + (set_local $$n$addr$133 + (get_local $$sub22) + ) + (set_local $$w$034 + (get_local $$incdec$ptr21) + ) + ) + (block + (set_local $$n$addr$1$lcssa + (get_local $$sub22) + ) + (set_local $$w$0$lcssa + (get_local $$incdec$ptr21) + ) + (set_local $label + (i32.const 11) + ) + (br $label$break$L11) + ) + ) + (br $while-in3) + ) + ) + (set_local $$n$addr$227 + (get_local $$n$addr$133) + ) + (set_local $$s$128 + (get_local $$w$034) + ) + ) + (block + (set_local $$n$addr$1$lcssa + (get_local $$n$addr$0$lcssa52) + ) + (set_local $$w$0$lcssa + (get_local $$s$0$lcssa53) + ) + (set_local $label + (i32.const 11) + ) + ) + ) + ) + (if + (i32.eq + (get_local $label) + (i32.const 11) + ) + (block + (set_local $$tobool2526 + (i32.eq + (get_local $$n$addr$1$lcssa) + (i32.const 0) + ) + ) + (if + (get_local $$tobool2526) + (block + (set_local $$n$addr$3 + (i32.const 0) + ) + (set_local $$s$2 + (get_local $$w$0$lcssa) + ) + (br $label$break$L8) + ) + (block + (set_local $$n$addr$227 + (get_local $$n$addr$1$lcssa) + ) + (set_local $$s$128 + (get_local $$w$0$lcssa) + ) + ) + ) + ) + ) + (loop $while-in5 + (block $while-out4 + (set_local $$7 + (i32.load8_s + (get_local $$s$128) + ) + ) + (set_local $$cmp28 + (i32.eq + (i32.shr_s + (i32.shl + (get_local $$7) + (i32.const 24) + ) + (i32.const 24) + ) + (i32.shr_s + (i32.shl + (get_local $$5) + (i32.const 24) + ) + (i32.const 24) + ) + ) + ) + (if + (get_local $$cmp28) + (block + (set_local $$n$addr$3 + (get_local $$n$addr$227) + ) + (set_local $$s$2 + (get_local $$s$128) + ) + (br $label$break$L8) + ) + ) + (set_local $$incdec$ptr33 + (i32.add + (get_local $$s$128) + (i32.const 1) + ) + ) + (set_local $$dec34 + (i32.add + (get_local $$n$addr$227) + (i32.const -1) + ) + ) + (set_local $$tobool25 + (i32.eq + (get_local $$dec34) + (i32.const 0) + ) + ) + (if + (get_local $$tobool25) + (block + (set_local $$n$addr$3 + (i32.const 0) + ) + (set_local $$s$2 + (get_local $$incdec$ptr33) + ) + (br $while-out4) + ) + (block + (set_local $$n$addr$227 + (get_local $$dec34) + ) + (set_local $$s$128 + (get_local $$incdec$ptr33) + ) + ) + ) + (br $while-in5) + ) + ) + ) + ) + ) + ) + ) + (set_local $$tobool36 + (i32.ne + (get_local $$n$addr$3) + (i32.const 0) + ) + ) + (set_local $$cond + (if i32 + (get_local $$tobool36) + (get_local $$s$2) + (i32.const 0) + ) + ) + (return + (get_local $$cond) + ) + ) (func $keepAlive (call $loads) (call $stores) @@ -995,6 +1575,18 @@ (i32.const 0) (i32.const 0) ) + (drop + (call $_memchr + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + (drop + (call $switch64TOOMUCH + (i64.const 0) + ) + ) ) (func $__emscripten_dceable_type_decls (drop |